From e663fb3543578bdcba7286e406131960aea3cb67 Mon Sep 17 00:00:00 2001 From: holivier Date: Sat, 30 May 2020 18:37:04 +0200 Subject: [PATCH 01/24] Import VueJs component from ofbizextra repository and cleaning of it --- vuejs/.gitignore | 4 + vuejs/LICENSE | 209 ++ vuejs/README.md | 33 + vuejs/config/VuejsUiLabels.xml | 30 + vuejs/data/VueJsSeedData.xml | 23 + .../groovyScripts/GetPortletAttributes.groovy | 54 + vuejs/groovyScripts/getCommonUiLabel.groovy | 55 + vuejs/groovyScripts/getHeaderInfo.groovy | 57 + vuejs/ofbiz-component.xml | 42 + ...-update-area-is-now-a-stringExpander.patch | 28 + ...d-use-when-for-xxx-event-update-area.patch | 218 ++ ...-in-updateArea-class-it-s-needed-to-.patch | 33 + ...http-method-in-link-and-in-on-field-.patch | 79 + ...ameters-as-children-of-on-field-even.patch | 29 + ...-and-set-watcher-for-container-level.patch | 220 ++ ...update-area-add-commit-as-event-type.patch | 39 + ...pdate-area-add-set-field-in-form-as-.patch | 42 + ...a-field-and-form-level-add-collapse-.patch | 65 + ...a-field-and-form-level-add-close-mod.patch | 63 + ...s-children-tag-of-html-tag-in-screen.patch | 232 ++ ...gement-name-and-tooltips-abstraction.patch | 103 + ...as-new-field-in-PortalPagePortlet-en.patch | 29 + ...ers-portlet-as-children-of-event-upd.patch | 127 + ...ample-portal-page-ExampleMgmtFrontJs.patch | 32 + vuejs/src/docs/asciidoc/FrontJsVueJs_en.adoc | 89 + vuejs/src/docs/asciidoc/Ui-POC.adoc | 515 ++++ .../asciidoc/_include/poc-situation_en.adoc | 43 + .../docs/asciidoc/_include/poc-todo_en.adoc | 73 + .../asciidoc/_include/poc-useCase_en.adoc | 28 + .../_include/poc-vuejs-details_en.adoc | 87 + .../docs/asciidoc/_include/poc-vuejs_en.adoc | 318 ++ .../ofbiz-Resource-REST-reference.adoc | 97 + .../renderer/frontjs/FrontJsFormRenderer.java | 2711 +++++++++++++++++ .../renderer/frontjs/FrontJsMenuRenderer.java | 279 ++ .../renderer/frontjs/FrontJsOutput.java | 268 ++ .../frontjs/FrontJsScreenRenderer.java | 722 +++++ .../frontjs/FrontJsScreenViewHandler.java | 133 + .../renderer/frontjs/FrontJsTreeRenderer.java | 256 ++ vuejs/template/vuejsStart.ftl | 4 + vuejs/tools/applyOfbiz2CommitPatchs.sh | 8 + vuejs/webapp/vuejs/.browserslistrc | 2 + vuejs/webapp/vuejs/.eslintrc.js | 17 + vuejs/webapp/vuejs/.gitignore | 24 + vuejs/webapp/vuejs/README.md | 29 + vuejs/webapp/vuejs/WEB-INF/web.xml | 36 + vuejs/webapp/vuejs/babel.config.js | 5 + vuejs/webapp/vuejs/package.json | 52 + vuejs/webapp/vuejs/postcss.config.js | 5 + vuejs/webapp/vuejs/src/components/App.vue | 52 + vuejs/webapp/vuejs/src/components/AppCpt.vue | 20 + .../webapp/vuejs/src/components/AppDialog.vue | 47 + vuejs/webapp/vuejs/src/components/AppLog.vue | 63 + vuejs/webapp/vuejs/src/components/AppMenu.vue | 377 +++ vuejs/webapp/vuejs/src/components/AppWait.vue | 33 + vuejs/webapp/vuejs/src/components/Portal.vue | 57 + vuejs/webapp/vuejs/src/components/Screen.vue | 79 + .../src/components/SeleniumInfoPanel.vue | 64 + .../vuejs/src/components/VueAsterisks.vue | 29 + .../webapp/vuejs/src/components/VueColumn.vue | 49 + .../src/components/VueColumnContainer.vue | 30 + .../vuejs/src/components/VueColumnPortlet.vue | 51 + .../vuejs/src/components/VueContainer.vue | 146 + .../vuejs/src/components/VueDateTimeField.vue | 184 ++ .../vuejs/src/components/VueDisplayField.vue | 181 ++ .../vuejs/src/components/VueDropDownField.vue | 110 + .../webapp/vuejs/src/components/VueError.vue | 18 + .../webapp/vuejs/src/components/VueField.vue | 23 + .../vuejs/src/components/VueFieldGroup.vue | 85 + .../vuejs/src/components/VueFieldRow.vue | 34 + .../src/components/VueFieldRowTitleCell.vue | 34 + .../src/components/VueFieldRowWidgetCell.vue | 34 + .../vuejs/src/components/VueFieldTitle.vue | 28 + vuejs/webapp/vuejs/src/components/VueForm.vue | 143 + .../src/components/VueFormatEmptySpace.vue | 13 + .../webapp/vuejs/src/components/VueHeader.vue | 34 + .../vuejs/src/components/VueHeaderRow.vue | 34 + .../vuejs/src/components/VueHeaderRowCell.vue | 34 + .../vuejs/src/components/VueHiddenField.vue | 69 + .../src/components/VueHorizontalSeparator.vue | 13 + .../src/components/VueHyperlinkField.vue | 330 ++ .../vuejs/src/components/VueItemRow.vue | 34 + .../vuejs/src/components/VueItemRowCell.vue | 37 + .../webapp/vuejs/src/components/VueLabel.vue | 31 + vuejs/webapp/vuejs/src/components/VueLink.vue | 264 ++ .../vuejs/src/components/VueListWrapper.vue | 48 + .../webapp/vuejs/src/components/VueLogin.vue | 77 + .../vuejs/src/components/VueLookupField.vue | 299 ++ vuejs/webapp/vuejs/src/components/VueMenu.vue | 48 + .../vuejs/src/components/VueMenuItem.vue | 49 + .../vuejs/src/components/VueMessageList.vue | 63 + .../vuejs/src/components/VueNavMenu.vue | 69 + .../vuejs/src/components/VueNavMenuItem.vue | 64 + .../src/components/VueNavMenuItemInline.vue | 61 + .../vuejs/src/components/VueNextPrev.vue | 124 + .../webapp/vuejs/src/components/VueOption.vue | 29 + .../vuejs/src/components/VuePasswordField.vue | 97 + .../src/components/VuePlatformSpecific.vue | 24 + .../vuejs/src/components/VuePortlet.vue | 127 + .../vuejs/src/components/VueRadioField.vue | 104 + .../vuejs/src/components/VueScreenlet.vue | 146 + .../vuejs/src/components/VueSingleWrapper.vue | 34 + .../vuejs/src/components/VueSortField.vue | 60 + .../vuejs/src/components/VueSubmitField.vue | 170 ++ .../webapp/vuejs/src/components/VueTable.vue | 34 + .../vuejs/src/components/VueTextAreaField.vue | 97 + .../vuejs/src/components/VueTextField.vue | 108 + .../vuejs/src/components/VueTextFindField.vue | 172 ++ .../webapp/vuejs/src/components/VueThead.vue | 34 + vuejs/webapp/vuejs/src/components/VueTr.vue | 34 + .../platformSpecific/ContactMech.vue | 1178 +++++++ .../ContactMech/EmailAddress.vue | 129 + .../ContactMech/FtpAddress.vue | 185 ++ .../platformSpecific/ContactMech/Generic.vue | 133 + .../ContactMech/PostalAddress.vue | 168 + .../ContactMech/TelecomNumber.vue | 142 + vuejs/webapp/vuejs/src/js/constants.js | 59 + vuejs/webapp/vuejs/src/js/icons.js | 99 + vuejs/webapp/vuejs/src/main.js | 229 ++ vuejs/webapp/vuejs/src/plugins/vuetify.js | 29 + vuejs/webapp/vuejs/src/store/index.js | 21 + .../vuejs/src/store/modules/backOfficeApi.js | 214 ++ vuejs/webapp/vuejs/src/store/modules/data.js | 133 + vuejs/webapp/vuejs/src/store/modules/form.js | 61 + vuejs/webapp/vuejs/src/store/modules/login.js | 144 + vuejs/webapp/vuejs/src/store/modules/ui.js | 342 +++ vuejs/webapp/vuejs/vue.config.js | 18 + vuejs/webcommon/vuejs-controller.xml | 105 + vuejs/widget/CommonScreens.xml | 213 ++ 128 files changed, 16285 insertions(+) create mode 100644 vuejs/.gitignore create mode 100644 vuejs/LICENSE create mode 100644 vuejs/README.md create mode 100644 vuejs/config/VuejsUiLabels.xml create mode 100644 vuejs/data/VueJsSeedData.xml create mode 100644 vuejs/groovyScripts/GetPortletAttributes.groovy create mode 100644 vuejs/groovyScripts/getCommonUiLabel.groovy create mode 100644 vuejs/groovyScripts/getHeaderInfo.groovy create mode 100644 vuejs/ofbiz-component.xml create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11757_0001-area-id-in-event-update-area-is-now-a-stringExpander.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11758_0001-Improvement-add-use-when-for-xxx-event-update-area.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11759_0001-add-toMap-method-in-updateArea-class-it-s-needed-to-.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11760_0001-Improved-Manage-http-method-in-link-and-in-on-field-.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11761_0001-Improved-add-parameters-as-children-of-on-field-even.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11763_0001-Add-set-area-and-set-watcher-for-container-level.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11764_0001-on-field-event-update-area-add-commit-as-event-type.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11765_0001-on-field-event-update-area-add-set-field-in-form-as-.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11766_0001-event-update-area-field-and-form-level-add-collapse-.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11767_0001-event-update-area-field-and-form-level-add-close-mod.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11768_0001-add-vuejs-as-children-tag-of-html-tag-in-screen.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11769_0001-Add-icon-management-name-and-tooltips-abstraction.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11770_0001-Add-watcherName-as-new-field-in-PortalPagePortlet-en.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11771_0001-add-auto-parameters-portlet-as-children-of-event-upd.patch create mode 100644 vuejs/ofbizCommit2add/OFBIZ-11999_0024-Add-Label-for-example-portal-page-ExampleMgmtFrontJs.patch create mode 100644 vuejs/src/docs/asciidoc/FrontJsVueJs_en.adoc create mode 100644 vuejs/src/docs/asciidoc/Ui-POC.adoc create mode 100644 vuejs/src/docs/asciidoc/_include/poc-situation_en.adoc create mode 100644 vuejs/src/docs/asciidoc/_include/poc-todo_en.adoc create mode 100644 vuejs/src/docs/asciidoc/_include/poc-useCase_en.adoc create mode 100644 vuejs/src/docs/asciidoc/_include/poc-vuejs-details_en.adoc create mode 100644 vuejs/src/docs/asciidoc/_include/poc-vuejs_en.adoc create mode 100644 vuejs/src/docs/asciidoc/ofbiz-Resource-REST-reference.adoc create mode 100644 vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsFormRenderer.java create mode 100644 vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsMenuRenderer.java create mode 100644 vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsOutput.java create mode 100644 vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsScreenRenderer.java create mode 100644 vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsScreenViewHandler.java create mode 100644 vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsTreeRenderer.java create mode 100644 vuejs/template/vuejsStart.ftl create mode 100755 vuejs/tools/applyOfbiz2CommitPatchs.sh create mode 100644 vuejs/webapp/vuejs/.browserslistrc create mode 100644 vuejs/webapp/vuejs/.eslintrc.js create mode 100644 vuejs/webapp/vuejs/.gitignore create mode 100644 vuejs/webapp/vuejs/README.md create mode 100644 vuejs/webapp/vuejs/WEB-INF/web.xml create mode 100644 vuejs/webapp/vuejs/babel.config.js create mode 100644 vuejs/webapp/vuejs/package.json create mode 100644 vuejs/webapp/vuejs/postcss.config.js create mode 100644 vuejs/webapp/vuejs/src/components/App.vue create mode 100644 vuejs/webapp/vuejs/src/components/AppCpt.vue create mode 100644 vuejs/webapp/vuejs/src/components/AppDialog.vue create mode 100644 vuejs/webapp/vuejs/src/components/AppLog.vue create mode 100644 vuejs/webapp/vuejs/src/components/AppMenu.vue create mode 100644 vuejs/webapp/vuejs/src/components/AppWait.vue create mode 100644 vuejs/webapp/vuejs/src/components/Portal.vue create mode 100644 vuejs/webapp/vuejs/src/components/Screen.vue create mode 100644 vuejs/webapp/vuejs/src/components/SeleniumInfoPanel.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueAsterisks.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueColumn.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueColumnContainer.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueColumnPortlet.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueContainer.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueDateTimeField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueDisplayField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueDropDownField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueError.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueFieldGroup.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueFieldRow.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueFieldRowTitleCell.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueFieldRowWidgetCell.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueFieldTitle.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueForm.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueFormatEmptySpace.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueHeader.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueHeaderRow.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueHeaderRowCell.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueHiddenField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueHorizontalSeparator.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueHyperlinkField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueItemRow.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueItemRowCell.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueLabel.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueLink.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueListWrapper.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueLogin.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueLookupField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueMenu.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueMenuItem.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueMessageList.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueNavMenu.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueNavMenuItem.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueNavMenuItemInline.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueNextPrev.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueOption.vue create mode 100644 vuejs/webapp/vuejs/src/components/VuePasswordField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VuePlatformSpecific.vue create mode 100644 vuejs/webapp/vuejs/src/components/VuePortlet.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueRadioField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueScreenlet.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueSingleWrapper.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueSortField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueSubmitField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueTable.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueTextAreaField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueTextField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueTextFindField.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueThead.vue create mode 100644 vuejs/webapp/vuejs/src/components/VueTr.vue create mode 100644 vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech.vue create mode 100644 vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/EmailAddress.vue create mode 100644 vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/FtpAddress.vue create mode 100644 vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/Generic.vue create mode 100644 vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/PostalAddress.vue create mode 100644 vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/TelecomNumber.vue create mode 100644 vuejs/webapp/vuejs/src/js/constants.js create mode 100644 vuejs/webapp/vuejs/src/js/icons.js create mode 100644 vuejs/webapp/vuejs/src/main.js create mode 100644 vuejs/webapp/vuejs/src/plugins/vuetify.js create mode 100644 vuejs/webapp/vuejs/src/store/index.js create mode 100644 vuejs/webapp/vuejs/src/store/modules/backOfficeApi.js create mode 100644 vuejs/webapp/vuejs/src/store/modules/data.js create mode 100644 vuejs/webapp/vuejs/src/store/modules/form.js create mode 100644 vuejs/webapp/vuejs/src/store/modules/login.js create mode 100644 vuejs/webapp/vuejs/src/store/modules/ui.js create mode 100644 vuejs/webapp/vuejs/vue.config.js create mode 100644 vuejs/webcommon/vuejs-controller.xml create mode 100644 vuejs/widget/CommonScreens.xml diff --git a/vuejs/.gitignore b/vuejs/.gitignore new file mode 100644 index 000000000..9af1d8009 --- /dev/null +++ b/vuejs/.gitignore @@ -0,0 +1,4 @@ +/.settings/ +/build/libs/ +/build/tmp/ +/build/ diff --git a/vuejs/LICENSE b/vuejs/LICENSE new file mode 100644 index 000000000..3fa394edc --- /dev/null +++ b/vuejs/LICENSE @@ -0,0 +1,209 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================= +This product bundles "xxxxxxxx", which is available +under the "XXXX" License. For details, see http://xxxxxxx + + diff --git a/vuejs/README.md b/vuejs/README.md new file mode 100644 index 000000000..61fddaad2 --- /dev/null +++ b/vuejs/README.md @@ -0,0 +1,33 @@ +vuejs plugin +============ + +This plugin is a Apache OFBiz plugin, part of a group of plugin which intended to enhance OFBiz to work +with the vue.js SPA (Single Page Application). + +Group of plugin contain: +- vuejs as the "framework" part +- examplefjs as example of usage +- flatgreyfjs as a dedicated theme to load only javascript needed (almost nothing) + +The 3 are in the ofbizextra/ofbizplugins/ gitlab repository + +At the moment it is at the Proof of Concept level. So sometine it's speaking about vue.js, sometime about frontjs +and sometimes about Portal sometine screen. + +It's goal is to be a base to a future Apache OFBiz plugin integrated to Apache OFBiz. + +The latest about this OFBiz plugin can be found in the documentation (see below) + +# Implementation +Have a look to install documentation https://ofbizextra.org/ofbizextra_adocs/docs/asciidoc/developer-manual.html#_poc_vuejs_renderer_installation + +## Summary For developers +1. clone the repo in the plugins folder of your OFBiz location for the 3 plugins +2. apply Jira patch waiting validation about compound-widget from git repo ofbizextra/ofbizJiraPatchAvailable +3. apply patch about Jira associated to vuejs renderer ((from ofbizCommit2add vuejs directory) +4. restart your OFBiz implementation +5. load data sets of the plugin via webtools/import (for the portalPage) + + +# Documentation +All documentation and detail about this plugin is on https://ofbizextra.org/ofbizextra_adocs/docs/asciidoc/developer-manual.html#_frontjs_portal \ No newline at end of file diff --git a/vuejs/config/VuejsUiLabels.xml b/vuejs/config/VuejsUiLabels.xml new file mode 100644 index 000000000..d9c0c751d --- /dev/null +++ b/vuejs/config/VuejsUiLabels.xml @@ -0,0 +1,30 @@ + + + + + Login Successful + Connexion réussie + + + Remember me + Se souvenir de moi + + diff --git a/vuejs/data/VueJsSeedData.xml b/vuejs/data/VueJsSeedData.xml new file mode 100644 index 000000000..ca644f886 --- /dev/null +++ b/vuejs/data/VueJsSeedData.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/vuejs/groovyScripts/GetPortletAttributes.groovy b/vuejs/groovyScripts/GetPortletAttributes.groovy new file mode 100644 index 000000000..263aed874 --- /dev/null +++ b/vuejs/groovyScripts/GetPortletAttributes.groovy @@ -0,0 +1,54 @@ + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Call service getPortletAttributes and put all attributes in context + +import org.apache.ofbiz.base.util.Debug +import org.apache.ofbiz.entity.GenericValue +import org.apache.ofbiz.entity.util.EntityQuery + +portalPageId = parameters.portalPageId +portalPortletId = parameters.portalPortletId +portletSeqId = parameters.portletSeqId + +// read attributes as default values +portletAttributes = EntityQuery.use(delegator) + .from("PortletAttribute") + .where("portalPageId", "_NA_", + "portalPortletId", portalPortletId, + "portletSeqId", "00000") + .queryList(); +def attributeMap = [:] +if (portletAttributes.size()>0) { + attributesIterator = portletAttributes.listIterator() + while (attributesIterator.hasNext()) { + attribute = attributesIterator.next() + attributeMap.put(attribute.get("attrName"), attribute.get("attrValue")) + } +} + +// now read user and standard attributes +portalPortletInMap = [portalPageId : parameters.portalPageId, portalPortletId : parameters.portalPortletId , portletSeqId : parameters.portletSeqId , userLogin : userLogin] +resultOutMap = dispatcher.runSync("getPortletAttributes", portalPortletInMap) +if (resultOutMap.attributeMap) + attributeMap.putAll((Map) resultOutMap.attributeMap) + +// load context with all attributes +context.putAll(attributeMap) diff --git a/vuejs/groovyScripts/getCommonUiLabel.groovy b/vuejs/groovyScripts/getCommonUiLabel.groovy new file mode 100644 index 000000000..e101163e8 --- /dev/null +++ b/vuejs/groovyScripts/getCommonUiLabel.groovy @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.ofbiz.base.util.UtilProperties +import org.apache.ofbiz.base.util.Debug + +uiLabelMap = UtilProperties.getResourceBundleMap("CommonUiLabels", locale) +uiLabelMap.addBottomResourceBundle("VuejsUiLabels") + +vuejsUiLabel = [:] +vuejsUiLabel.add = uiLabelMap.CommonAdd +vuejsUiLabel.cancel = uiLabelMap.CommonCancel +vuejsUiLabel.cancelAll = uiLabelMap.CommonCancelAll +vuejsUiLabel.collapseToolTip = uiLabelMap["CommonCollapse"] // screenletCollapse +vuejsUiLabel.confirmButton = uiLabelMap.FormFieldTitle_confirmButton +vuejsUiLabel.confirmDelete = uiLabelMap.CommonConfirmDelete +vuejsUiLabel.date = uiLabelMap.CommonDate + +vuejsUiLabel.expandToolTip = uiLabelMap.CommonExpand // screenletExpand +vuejsUiLabel.expire = uiLabelMap.CommonExpire +vuejsUiLabel.forgotYourPassword = uiLabelMap.CommonForgotYourPassword +vuejsUiLabel.login = uiLabelMap.CommonLogin +vuejsUiLabel.loginSuccessMessage = uiLabelMap.VuejsLoginSuccessMessage +//FormFieldTitle_expireButton +vuejsUiLabel.now = uiLabelMap.CommonNow +vuejsUiLabel.ofLabel = uiLabelMap.CommonOf // pagination CommonRequired +vuejsUiLabel.password = uiLabelMap.CommonPassword +vuejsUiLabel.rememberMe = uiLabelMap.VuejsRememberMe +vuejsUiLabel.registred = uiLabelMap.CommonRegistered +vuejsUiLabel.required = uiLabelMap.CommonRequired +vuejsUiLabel.showAll = uiLabelMap.CommonShowAll +vuejsUiLabel.summary = uiLabelMap.CommonSummary +vuejsUiLabel.time = uiLabelMap.CommonTime +vuejsUiLabel.userName = uiLabelMap.CommonUsername +request.setAttribute("commonUiLabels", vuejsUiLabel) +return "success" + +// + diff --git a/vuejs/groovyScripts/getHeaderInfo.groovy b/vuejs/groovyScripts/getHeaderInfo.groovy new file mode 100644 index 000000000..2afdecba2 --- /dev/null +++ b/vuejs/groovyScripts/getHeaderInfo.groovy @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.ofbiz.base.util.UtilProperties +import org.apache.ofbiz.base.component.ComponentConfig +import org.apache.ofbiz.base.util.Debug +import org.apache.ofbiz.base.util.StringUtil; + +ofbizServerName = request.getAttribute("_serverId") ? request.getAttribute("_serverId") : "default-server" +Debug.logInfo("ofbizServerName="+ofbizServerName, "getHeaderInfo") +displayApps = org.apache.ofbiz.webapp.control.LoginWorker.getAppBarWebInfos(security, userLogin, ofbizServerName, "main") +displaySecondaryApps = org.apache.ofbiz.webapp.control.LoginWorker.getAppBarWebInfos(security, userLogin, ofbizServerName, "secondary") + +apps = [] +primaryApps = [] +secondaryApps = [] +for (ComponentConfig.WebappInfo displayApp : displayApps) { + appli = new HashMap() + appli.name = displayApp.name + servletPath = org.apache.ofbiz.webapp.WebAppUtil.getControlServletPath(displayApp) + appli.thisURL = StringUtil.wrapString(servletPath).toString() +// appli.description = uiLabelMap.get(displayApp.description) + appli.description = displayApp.description + primaryApps.add(appli) + apps.add(appli) +} +for (ComponentConfig.WebappInfo displaySecondaryApp : displaySecondaryApps) { + appli = new HashMap() + appli.name = displaySecondaryApp.name + servletPath = org.apache.ofbiz.webapp.WebAppUtil.getControlServletPath(displaySecondaryApp) + appli.thisURL = StringUtil.wrapString(servletPath).toString() +// appli.description = uiLabelMap.get(displaySecondaryApp.description) + appli.description = displaySecondaryApp.description + secondaryApps.add(appli) + apps.add(appli) +} +request.setAttribute("displayApps", apps) +request.setAttribute("locale",locale.getProperties()) +return "success" + + diff --git a/vuejs/ofbiz-component.xml b/vuejs/ofbiz-component.xml new file mode 100644 index 000000000..6f7d81724 --- /dev/null +++ b/vuejs/ofbiz-component.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11757_0001-area-id-in-event-update-area-is-now-a-stringExpander.patch b/vuejs/ofbizCommit2add/OFBIZ-11757_0001-area-id-in-event-update-area-is-now-a-stringExpander.patch new file mode 100644 index 000000000..8c945a09a --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11757_0001-area-id-in-event-update-area-is-now-a-stringExpander.patch @@ -0,0 +1,28 @@ +From 1a0a88ad1507596a320f3f27758c1c1f7aec63a2 Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 11:45:19 +0200 +Subject: [PATCH 09/25] Improved: area-id in event-update-area is now a stringExpander (OFBIZ-11757) + +It's now possible to use ${ for area-id +--- + .../widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +index 8c4c4a1f94..29d660f248 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +@@ -1763,6 +1763,10 @@ public abstract class ModelForm extends ModelWidget { + return areaId; + } + ++ public String getAreaId(Map context) { ++ return FlexibleStringExpander.expandString(areaId, context); ++ } ++ + public String getAreaTarget(Map context) { + return FlexibleStringExpander.expandString(areaTarget, context); + } +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11758_0001-Improvement-add-use-when-for-xxx-event-update-area.patch b/vuejs/ofbizCommit2add/OFBIZ-11758_0001-Improvement-add-use-when-for-xxx-event-update-area.patch new file mode 100644 index 000000000..893222390 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11758_0001-Improvement-add-use-when-for-xxx-event-update-area.patch @@ -0,0 +1,218 @@ +From 3b1a05531772e5026c020dbb493fddb56f8e0f4c Mon Sep 17 00:00:00 2001 +From: holivier +Date: Fri, 22 May 2020 11:10:45 +0200 +Subject: [PATCH 07/22] Improved: add use-when for xxx-event-update-area (OFBIZ-11758) + +1. add "use-when" management in the xsd and in the modelForm.UpdateArea, +2. add in modelForm a static method to return a valid list of updateArea +from a list of updateArea and context +3. duplicate all methods returning a list of updateArea, by same name +but with context as parameters which return only valid updateArea list +(ex: in formFieldBuilder for getOnClickUpdateAreas and +getOnChangeUpdateAreas) +--- + framework/widget/dtd/widget-form.xsd | 2 + + .../org/apache/ofbiz/widget/model/ModelForm.java | 61 +++++++++++++++++++++- + .../apache/ofbiz/widget/model/ModelFormField.java | 16 ++++++ + .../ofbiz/widget/model/ModelFormFieldBuilder.java | 16 ++++++ + 4 files changed, 94 insertions(+), 1 deletion(-) + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index 9eb3efd8b7..08e0c9c4e3 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -599,6 +599,7 @@ under the License. + + + ++ + + + +@@ -1535,6 +1536,7 @@ under the License. + + + ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +index 919791d65f..b2f01f93b3 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +@@ -1067,16 +1067,40 @@ public abstract class ModelForm extends ModelWidget { + public List getOnPaginateUpdateAreas() { + return this.onPaginateUpdateAreas; + } ++ /** ++ * Returns the list of ModelForm.UpdateArea objects to activate on a pagination event and which have no use-when or use-when = true. ++ * @param context ++ * @return ++ */ ++ public List getOnPaginateUpdateAreas(Map context) { ++ return getValidUpdateAreas(this.onPaginateUpdateAreas, context); ++ } + + public List getOnSortColumnUpdateAreas() { + return this.onSortColumnUpdateAreas; + } ++ /** ++ * Returns the list of ModelForm.UpdateArea objects to activate on a column sort event and which have no use-when or use-when = true. ++ * @param context ++ * @return ++ */ ++ public List getOnSortColumnUpdateAreas(Map context) { ++ return getValidUpdateAreas(this.onSortColumnUpdateAreas, context); ++ } + +- /* Returns the list of ModelForm.UpdateArea objects. ++ /** Returns the list of ModelForm.UpdateArea objects. + */ + public List getOnSubmitUpdateAreas() { + return this.onSubmitUpdateAreas; + } ++ /** ++ * Returns the list of ModelForm.UpdateArea objects to activate on a submit event and which have no use-when or use-when = true. ++ * @param context ++ * @return ++ */ ++ public List getOnSubmitUpdateAreas(Map context) { ++ return getValidUpdateAreas(this.onSubmitUpdateAreas, context); ++ } + + public String getOverrideListSize() { + return overrideListSize.getOriginal(); +@@ -1695,6 +1719,7 @@ public abstract class ModelForm extends ModelWidget { + private final String eventType; + private final String areaId; + private final String areaTarget; ++ private final String useWhen; + private final String defaultServiceName; + private final String defaultEntityName; + private final CommonWidgetModels.AutoEntityParameters autoEntityParameters; +@@ -1713,6 +1738,7 @@ public abstract class ModelForm extends ModelWidget { + this.eventType = updateAreaElement.getAttribute("event-type"); + this.areaId = updateAreaElement.getAttribute("area-id"); + this.areaTarget = updateAreaElement.getAttribute("area-target"); ++ this.useWhen = updateAreaElement.getAttribute("use-when"); + this.defaultServiceName = defaultServiceName; + this.defaultEntityName = defaultEntityName; + List parameterElementList = UtilXml.childElementList(updateAreaElement, "parameter"); +@@ -1747,6 +1773,7 @@ public abstract class ModelForm extends ModelWidget { + this.eventType = eventType; + this.areaId = areaId; + this.areaTarget = areaTarget; ++ this.useWhen = null; + this.defaultServiceName = null; + this.defaultEntityName = null; + this.parameterList = Collections.emptyList(); +@@ -1775,6 +1802,10 @@ public abstract class ModelForm extends ModelWidget { + return eventType; + } + ++ public String getUseWhen() { ++ return useWhen; ++ } ++ + public Map getParameterMap(Map context) { + Map fullParameterMap = new HashMap<>(); + if (autoServiceParameters != null) { +@@ -1819,4 +1850,32 @@ public abstract class ModelForm extends ModelWidget { + return parameterList; + } + } ++ ++ static protected List getValidUpdateAreas(List updateAreas, Map context) { ++ if (updateAreas == null) return null; ++ ArrayList updateAreasValid = new ArrayList<>(); ++ try { ++ for (ModelForm.UpdateArea updateArea : updateAreas) { ++ String useWhen = FlexibleStringExpander.expandString(updateArea.getUseWhen(), context); ++ if (useWhen != null && !useWhen.isEmpty()) { ++ Object retVal = GroovyUtil.eval(StringUtil.convertOperatorSubstitutions(useWhen),context); ++ // retVal should be a Boolean, if not something weird is up... ++ if (retVal instanceof Boolean) { ++ Boolean boolVal = (Boolean) retVal; ++ if (boolVal) updateAreasValid.add(updateArea); ++ } else { ++ throw new IllegalArgumentException("Return value from updateArea condition eval was not a Boolean: " ++ + retVal.getClass().getName() + " [" + retVal + "] of updateArea " + updateArea.toString()); ++ } ++ } else { ++ updateAreasValid.add(updateArea); ++ } ++ } ++ } catch (CompilationFailedException e) { ++ String errmsg = "Error evaluating Groovy target conditions on updateArea "; ++ Debug.logError(e, errmsg, MODULE); ++ throw new IllegalArgumentException(errmsg); ++ } ++ return updateAreasValid; ++ } + } +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormField.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormField.java +index 03264a4fe3..a15569beba 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormField.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormField.java +@@ -540,10 +540,26 @@ public class ModelFormField { + public List getOnChangeUpdateAreas() { + return onChangeUpdateAreas; + } ++ /** ++ * Returns the list of ModelForm.UpdateArea objects to activate on a change event and which have no use-when or use-when = true. ++ * @param context ++ * @return ++ */ ++ public List getOnChangeUpdateAreas(Map context) { ++ return ModelForm.getValidUpdateAreas(onChangeUpdateAreas, context); ++ } + + public List getOnClickUpdateAreas() { + return onClickUpdateAreas; + } ++ /** ++ * Returns the list of ModelForm.UpdateArea objects to activate on a click event and which have no use-when or use-when = true. ++ * @param context ++ * @return ++ */ ++ public List getOnClickUpdateAreas(Map context) { ++ return ModelForm.getValidUpdateAreas(onClickUpdateAreas, context); ++ } + + public FlexibleStringExpander getParameterName() { + return parameterName; +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +index dfde8c25be..06f2397c68 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +@@ -444,10 +444,26 @@ public class ModelFormFieldBuilder { + public List getOnChangeUpdateAreas() { + return onChangeUpdateAreas; + } ++ /** ++ * Returns the list of ModelForm.UpdateArea objects to activate on a change event and which have no use-when or use-when = true. ++ * @param context ++ * @return ++ */ ++ public List getOnChangeUpdateAreas(Map context) { ++ return ModelForm.getValidUpdateAreas(onChangeUpdateAreas, context); ++ } + + public List getOnClickUpdateAreas() { + return onClickUpdateAreas; + } ++ /** ++ * Returns the list of ModelForm.UpdateArea objects to activate on a click event and which have no use-when or use-when = true. ++ * @param context ++ * @return ++ */ ++ public List getOnClickUpdateAreas(Map context) { ++ return ModelForm.getValidUpdateAreas(onClickUpdateAreas, context); ++ } + + public FlexibleStringExpander getParameterName() { + return parameterName; +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11759_0001-add-toMap-method-in-updateArea-class-it-s-needed-to-.patch b/vuejs/ofbizCommit2add/OFBIZ-11759_0001-add-toMap-method-in-updateArea-class-it-s-needed-to-.patch new file mode 100644 index 000000000..cb2fd6547 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11759_0001-add-toMap-method-in-updateArea-class-it-s-needed-to-.patch @@ -0,0 +1,33 @@ +From f3982d88b3f77e1f7af66beb31f8e648b3b99e75 Mon Sep 17 00:00:00 2001 +From: holivier +Date: Mon, 25 May 2020 17:50:22 +0200 +Subject: [PATCH 08/22] Improved: add toMap() method in updateArea class, it's needed to + send a updateArea list in json format (OFBIZ-11759) + +--- + .../src/main/java/org/apache/ofbiz/widget/model/ModelForm.java | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +index b2f01f93b3..0e3b609efd 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +@@ -1849,6 +1849,15 @@ public abstract class ModelForm extends ModelWidget { + public List getParameterList() { + return parameterList; + } ++ ++ public Map toMap(Map context) { ++ Map map = new HashMap<>(); ++ map.put("eventType", this.getEventType()); ++ map.put("areaId", FlexibleStringExpander.expandString(this.getAreaId(), context)); ++ map.put("areaTarget", FlexibleStringExpander.expandString(this.getAreaTarget(), context)); ++ map.put("parameterMap", this.getParameterMap(context)); ++ return map; ++ } + } + + static protected List getValidUpdateAreas(List updateAreas, Map context) { +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11760_0001-Improved-Manage-http-method-in-link-and-in-on-field-.patch b/vuejs/ofbizCommit2add/OFBIZ-11760_0001-Improved-Manage-http-method-in-link-and-in-on-field-.patch new file mode 100644 index 000000000..2c42b380c --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11760_0001-Improved-Manage-http-method-in-link-and-in-on-field-.patch @@ -0,0 +1,79 @@ +From 7f2b9b28a42ccb2ce28a9877e5e208581b3d8cc0 Mon Sep 17 00:00:00 2001 +From: holivier +Date: Mon, 25 May 2020 18:10:28 +0200 +Subject: [PATCH 09/22] Improved: Manage http method in link and in + on-field-event-update-area (OFBIZ-11760) + +In link, added in url-mode and +in on-field-event-update-area added in event-type. + +Add post,put, delete (get is the default) +--- + framework/widget/dtd/widget-common.xsd | 3 +++ + framework/widget/dtd/widget-form.xsd | 3 +++ + .../src/main/java/org/apache/ofbiz/widget/model/ModelForm.java | 4 +++- + .../java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java | 5 +++++ + 4 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/framework/widget/dtd/widget-common.xsd b/framework/widget/dtd/widget-common.xsd +index bd23e32046..eb73280c7d 100644 +--- a/framework/widget/dtd/widget-common.xsd ++++ b/framework/widget/dtd/widget-common.xsd +@@ -566,6 +566,9 @@ under the License. + + + ++ ++ ++ + + + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index 08e0c9c4e3..4b068d2284 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -1531,6 +1531,9 @@ under the License. + + + ++ ++ ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +index 0e3b609efd..d831077c6e 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +@@ -434,7 +434,9 @@ public abstract class ModelForm extends ModelWidget { + } else { + onPaginateUpdateAreas.add(updateArea); + } +- } else if ("submit".equals(updateArea.getEventType())) { ++ } else if ("submit".equals(updateArea.getEventType()) ++ ||"post".equals(updateArea.getEventType()) ++ ) { + int index = onSubmitUpdateAreas.indexOf(updateArea); + if (index != -1) { + onSubmitUpdateAreas.set(index, updateArea); +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +index 06f2397c68..f6f30289c4 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +@@ -215,6 +215,11 @@ public class ModelFormFieldBuilder { + onChangeUpdateAreas.add(updateArea); + } else if ("click".equals(updateArea.getEventType())) { + onClickUpdateAreas.add(updateArea); ++ } else if ("post".equals(updateArea.getEventType()) ++ || "put".equals(updateArea.getEventType()) ++ || "delete".equals(updateArea.getEventType()) ++ ) { ++ onClickUpdateAreas.add(updateArea); + } + } else { + if (this.fieldType != null) { +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11761_0001-Improved-add-parameters-as-children-of-on-field-even.patch b/vuejs/ofbizCommit2add/OFBIZ-11761_0001-Improved-add-parameters-as-children-of-on-field-even.patch new file mode 100644 index 000000000..af3d023da --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11761_0001-Improved-add-parameters-as-children-of-on-field-even.patch @@ -0,0 +1,29 @@ +From 91acd64255fb8681f43f40150ed687525dba723f Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 14:48:32 +0200 +Subject: [PATCH 10/22] Improved: add parameters as children of + on-field-event-update-area, same as for on-event-update-area (OFBIZ-11761) + +Not necessary to modify model*.java because it's manage by update-area +object in the two case +--- + framework/widget/dtd/widget-form.xsd | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index 4b068d2284..a1aba90f14 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -1526,6 +1526,9 @@ under the License. + Area to be updated when a field event occurs. + + ++ ++ ++ + + + +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11763_0001-Add-set-area-and-set-watcher-for-container-level.patch b/vuejs/ofbizCommit2add/OFBIZ-11763_0001-Add-set-area-and-set-watcher-for-container-level.patch new file mode 100644 index 000000000..12b251284 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11763_0001-Add-set-area-and-set-watcher-for-container-level.patch @@ -0,0 +1,220 @@ +From 63e51bce356547106dfebea6fd9138d3e14fd40d Mon Sep 17 00:00:00 2001 +From: holivier +Date: Mon, 25 May 2020 20:16:07 +0200 +Subject: [PATCH 14/25] Improved: Add set-area and set-watcher for container level (OFBIZ-11763) + +* add attribute watcher-name for tag container +* add in link-type enum set-area, refresh-area and set-watcher, +refresh-watcher +* add in xxx-event-update-area, in event-type enum set-area, +refresh-area and set-watcher, refresh-watcher + +Concept is: +1. container is the main piece of dynamics screen (update a part of +screen on event), a new attribute is added watcher-name, and existing +one auto-update-target is using as target + +2. There are two/four new "action" usable by link - hyperlink + * set-area (and refresh-area) + * set-watcher (and refresh-watcher) + +3. set-area is used to update a containerId, and, most of the time, uses +only for included containerId +set-watcher put some "parameters" in a watcher-name + +4. when the content of a whatcher-name change, all containerId which +watch it, use their auto-update-target as target and watcher-name +content as parameters to update the container content. + +5. refresh-area is similar with set-area but target used will be the +auto-update-target define at the container level and, if a watcher-name +is define for this container, it's content will be used as parameters +(for solving {} in target or as parameters if no {} in target. +--- + framework/widget/dtd/widget-common.xsd | 14 +++++++ + framework/widget/dtd/widget-form.xsd | 44 ++++++++++++++++++++++ + framework/widget/dtd/widget-screen.xsd | 5 +++ + .../org/apache/ofbiz/widget/model/ModelForm.java | 4 ++ + .../ofbiz/widget/model/ModelFormFieldBuilder.java | 4 ++ + .../ofbiz/widget/model/ModelScreenWidget.java | 6 +++ + 6 files changed, 77 insertions(+) + +diff --git a/framework/widget/dtd/widget-common.xsd b/framework/widget/dtd/widget-common.xsd +index eb73280c7d..0fd32d3334 100644 +--- a/framework/widget/dtd/widget-common.xsd ++++ b/framework/widget/dtd/widget-common.xsd +@@ -559,6 +559,20 @@ under the License. + + + ++ ++ ++ ++ Usable only in menu link, for FrontJs action, target and target-windows are used. ++ ++ ++ ++ ++ ++ ++ Usable only in menu link, for FrontJs action, only target-windows is used (as watcherName). ++ ++ ++ + + + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index a1aba90f14..fe57b0dc47 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -589,11 +589,34 @@ under the License. + + + ++ ++ set-area and set-watcher are specifics for VueJs and are put onSubmitUpdateAreas list. ++ submit, set-area and set-watcher are activate when submit button is clicked. ++ ++ + + + + + ++ ++ ++ ++ set-area is activate when submit button is clicked, it uses area-id as area to update and area-target as target to call. ++ Parameters area read in included parameter tag or all form fields if parameters is empty. ++ ++ ++ ++ ++ ++ ++ refresh-area is activate when submit button is clicked, it's same as set-area but areaId should be a container with a ++ auto-update-target, because refresh-area will use it to refresh area and parameters in the watcher associated for call. ++ ++ ++ ++ ++ + + + +@@ -1530,6 +1553,9 @@ under the License. + + + ++ ++ post, put, delete, setArea and setWatcher are specifics for VueJs on hyperlink (or link) and are put onClickUpdateAreas list. ++ + + + +@@ -1537,6 +1563,24 @@ under the License. + + + ++ ++ ++ ++ set-area is activate when field is clicked, it uses area-id as area to update and area-target as target to call. ++ Parameters area read in included parameter tag or all form fields if parameters is empty. ++ ++ ++ ++ ++ ++ ++ refresh-area is activate when field is clicked, it's same as set-area but areaId should be a container with a ++ auto-update-target, because refresh-area will use it to refresh area and parameters in the watcher associated for call. ++ ++ ++ ++ ++ + + + +diff --git a/framework/widget/dtd/widget-screen.xsd b/framework/widget/dtd/widget-screen.xsd +index 087809b15c..b86feed004 100644 +--- a/framework/widget/dtd/widget-screen.xsd ++++ b/framework/widget/dtd/widget-screen.xsd +@@ -232,6 +232,11 @@ under the License. + The auto-update interval, in seconds. + + ++ ++ ++ Name of the watcher, which will trigger auto-update when it changes. ++ ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +index 61562ff7b7..4976778cd8 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +@@ -436,6 +436,10 @@ public abstract class ModelForm extends ModelWidget { + } + } else if ("submit".equals(updateArea.getEventType()) + ||"post".equals(updateArea.getEventType()) ++ ||"set-area".equals(updateArea.getEventType()) ++ ||"refresh-area".equals(updateArea.getEventType()) ++ ||"set-watcher".equals(updateArea.getEventType()) ++ ||"refresh-watcher".equals(updateArea.getEventType()) + ) { + int index = onSubmitUpdateAreas.indexOf(updateArea); + if (index != -1) { +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +index 7ac1c0023e..42c05dbe05 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +@@ -218,6 +218,10 @@ public class ModelFormFieldBuilder { + } else if ("post".equals(updateArea.getEventType()) + || "put".equals(updateArea.getEventType()) + || "delete".equals(updateArea.getEventType()) ++ || "set-area".equals(updateArea.getEventType()) ++ || "refresh-area".equals(updateArea.getEventType()) ++ || "set-watcher".equals(updateArea.getEventType()) ++ || "refresh-watcher".equals(updateArea.getEventType()) + ) { + onClickUpdateAreas.add(updateArea); + } +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java +index d0af4947ad..372b253e47 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java +@@ -438,6 +438,7 @@ public abstract class ModelScreenWidget extends ModelWidget { + private final FlexibleStringExpander styleExdr; + private final FlexibleStringExpander autoUpdateTargetExdr; + private final FlexibleStringExpander autoUpdateInterval; ++ private final FlexibleStringExpander watcherNameExdr; + private final List subWidgets; + + public Container(ModelScreen modelScreen, Element containerElement) { +@@ -446,6 +447,7 @@ public abstract class ModelScreenWidget extends ModelWidget { + this.typeExdr = FlexibleStringExpander.getInstance(containerElement.getAttribute("type")); + this.styleExdr = FlexibleStringExpander.getInstance(containerElement.getAttribute("style")); + this.autoUpdateTargetExdr = FlexibleStringExpander.getInstance(containerElement.getAttribute("auto-update-target")); ++ this.watcherNameExdr = FlexibleStringExpander.getInstance(containerElement.getAttribute("watcher-name")); + String autoUpdateInterval = containerElement.getAttribute("auto-update-interval"); + if (autoUpdateInterval.isEmpty()) { + autoUpdateInterval = "2"; +@@ -488,6 +490,10 @@ public abstract class ModelScreenWidget extends ModelWidget { + return this.autoUpdateTargetExdr.expandString(context); + } + ++ public String getWatcherNameExdr(Map context) { ++ return this.watcherNameExdr.expandString(context); ++ } ++ + public String getAutoUpdateInterval(Map context) { + return this.autoUpdateInterval.expandString(context); + } +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11764_0001-on-field-event-update-area-add-commit-as-event-type.patch b/vuejs/ofbizCommit2add/OFBIZ-11764_0001-on-field-event-update-area-add-commit-as-event-type.patch new file mode 100644 index 000000000..3bce53b10 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11764_0001-on-field-event-update-area-add-commit-as-event-type.patch @@ -0,0 +1,39 @@ +From 576edb7eca1869909c8ec118fe6b16f5d5fbd505 Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 10:59:30 +0200 +Subject: [PATCH 12/22] Improved: on-field-event-update-area: add commit as + event-type (OFBIZ-11764) + +it's a click event, when click the form is submitted +--- + framework/widget/dtd/widget-form.xsd | 1 + + .../main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index fe57b0dc47..f171869dff 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -1581,6 +1581,7 @@ under the License. + + + ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +index cfcba807eb..04cac8497a 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +@@ -222,6 +222,7 @@ public class ModelFormFieldBuilder { + || "refresh-area".equals(updateArea.getEventType()) + || "set-watcher".equals(updateArea.getEventType()) + || "refresh-watcher".equals(updateArea.getEventType()) ++ || "submit".equals(updateArea.getEventType()) + ) { + onClickUpdateAreas.add(updateArea); + } +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11765_0001-on-field-event-update-area-add-set-field-in-form-as-.patch b/vuejs/ofbizCommit2add/OFBIZ-11765_0001-on-field-event-update-area-add-set-field-in-form-as-.patch new file mode 100644 index 000000000..d32272e79 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11765_0001-on-field-event-update-area-add-set-field-in-form-as-.patch @@ -0,0 +1,42 @@ +From 36c9b862cbc89bb74e513bfe572d1e8d405da84a Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 11:07:47 +0200 +Subject: [PATCH 13/22] Improved: on-field-event-update-area: add + set-field-in-form as event-type (OFBIZ-11765) + +it's a click event, when click the field is update, it's useful in +lookup form to update field from which the lookup has been call + +No modification in renderer in this commit +--- + framework/widget/dtd/widget-form.xsd | 1 + + .../main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index f171869dff..d2a02a3ec4 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -1582,6 +1582,7 @@ under the License. + + + ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +index 04cac8497a..e8ec357c19 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +@@ -223,6 +223,7 @@ public class ModelFormFieldBuilder { + || "set-watcher".equals(updateArea.getEventType()) + || "refresh-watcher".equals(updateArea.getEventType()) + || "submit".equals(updateArea.getEventType()) ++ || "set-field-in-form".equals(updateArea.getEventType()) + ) { + onClickUpdateAreas.add(updateArea); + } +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11766_0001-event-update-area-field-and-form-level-add-collapse-.patch b/vuejs/ofbizCommit2add/OFBIZ-11766_0001-event-update-area-field-and-form-level-add-collapse-.patch new file mode 100644 index 000000000..d37836932 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11766_0001-event-update-area-field-and-form-level-add-collapse-.patch @@ -0,0 +1,65 @@ +From b6cd7a29a4d8b2a3f6290873fc3eee7389e9f174 Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 11:16:52 +0200 +Subject: [PATCH 14/22] Improved: event-update-area (field and form level): add + collapse as event-type (OFBIZ-11766) + +it's a click event, when click all the screenlet with the areaId are +collapse + +No modification in renderer in this commit +area-target can be used to detail action to do toggle, collapse or +expand +--- + framework/widget/dtd/widget-form.xsd | 2 ++ + .../widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java | 1 + + .../main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java | 1 + + 3 files changed, 4 insertions(+) + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index d2a02a3ec4..f850de9321 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -617,6 +617,7 @@ under the License. + + + ++ + + + +@@ -1583,6 +1584,7 @@ under the License. + + + ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +index 0a1496fb23..97982c55c0 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +@@ -440,6 +440,7 @@ public abstract class ModelForm extends ModelWidget { + ||"refresh-area".equals(updateArea.getEventType()) + ||"set-watcher".equals(updateArea.getEventType()) + ||"refresh-watcher".equals(updateArea.getEventType()) ++ ||"collapse".equals(updateArea.getEventType()) + ) { + int index = onSubmitUpdateAreas.indexOf(updateArea); + if (index != -1) { +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +index e8ec357c19..0e5fafb551 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +@@ -224,6 +224,7 @@ public class ModelFormFieldBuilder { + || "refresh-watcher".equals(updateArea.getEventType()) + || "submit".equals(updateArea.getEventType()) + || "set-field-in-form".equals(updateArea.getEventType()) ++ || "collapse".equals(updateArea.getEventType()) + ) { + onClickUpdateAreas.add(updateArea); + } +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11767_0001-event-update-area-field-and-form-level-add-close-mod.patch b/vuejs/ofbizCommit2add/OFBIZ-11767_0001-event-update-area-field-and-form-level-add-close-mod.patch new file mode 100644 index 000000000..7235ae217 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11767_0001-event-update-area-field-and-form-level-add-close-mod.patch @@ -0,0 +1,63 @@ +From b172a773a74e589eca7ac3829362d35844efacff Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 11:21:14 +0200 +Subject: [PATCH 15/22] Improved: event-update-area (field and form level): add + close-modal as event-type (OFBIZ-11767) + +it's a click event, when click one or all open modal area closed. +it's use for lookup and for modal created with link with layered-modal + +No modification in renderer in this commit +--- + framework/widget/dtd/widget-form.xsd | 2 ++ + .../widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java | 1 + + .../main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java | 1 + + 3 files changed, 4 insertions(+) + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index f850de9321..f82a0b2f44 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -618,6 +618,7 @@ under the License. + + + ++ + + + +@@ -1585,6 +1586,7 @@ under the License. + + + ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +index 97982c55c0..3dd94b21ce 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +@@ -441,6 +441,7 @@ public abstract class ModelForm extends ModelWidget { + ||"set-watcher".equals(updateArea.getEventType()) + ||"refresh-watcher".equals(updateArea.getEventType()) + ||"collapse".equals(updateArea.getEventType()) ++ ||"close-modal".equals(updateArea.getEventType()) + ) { + int index = onSubmitUpdateAreas.indexOf(updateArea); + if (index != -1) { +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +index 0e5fafb551..dbefb0c3ba 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java +@@ -225,6 +225,7 @@ public class ModelFormFieldBuilder { + || "submit".equals(updateArea.getEventType()) + || "set-field-in-form".equals(updateArea.getEventType()) + || "collapse".equals(updateArea.getEventType()) ++ || "close-modal".equals(updateArea.getEventType()) + ) { + onClickUpdateAreas.add(updateArea); + } +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11768_0001-add-vuejs-as-children-tag-of-html-tag-in-screen.patch b/vuejs/ofbizCommit2add/OFBIZ-11768_0001-add-vuejs-as-children-tag-of-html-tag-in-screen.patch new file mode 100644 index 000000000..cf8b95fd0 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11768_0001-add-vuejs-as-children-tag-of-html-tag-in-screen.patch @@ -0,0 +1,232 @@ +From bfe06f0b169d8862a0f0a20bfeb02ffa6e21352c Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 11:43:08 +0200 +Subject: [PATCH 19/25] Improved: add vuejs as children tag of html tag in screen (OFBIZ-11768) + +this tag is used to call vuejs specifics component in the standard vuejs +application (the vuejs renderer). +vuejs specifics components are similar to ftl files in html standard +renderer. +--- + framework/widget/dtd/widget-screen.xsd | 9 +++ + .../widget/artifact/ArtifactInfoGatherer.java | 5 ++ + .../org/apache/ofbiz/widget/model/HtmlWidget.java | 3 + + .../ofbiz/widget/model/ModelScreenWidget.java | 65 ++++++++++++++++++++++ + .../ofbiz/widget/model/ModelWidgetVisitor.java | 2 + + .../ofbiz/widget/model/XmlWidgetVisitor.java | 11 ++++ + .../widget/renderer/ScreenStringRenderer.java | 1 + + .../widget/renderer/macro/MacroScreenRenderer.java | 4 ++ + 8 files changed, 100 insertions(+) + +diff --git a/framework/widget/dtd/widget-screen.xsd b/framework/widget/dtd/widget-screen.xsd +index b86feed004..c6586aed08 100644 +--- a/framework/widget/dtd/widget-screen.xsd ++++ b/framework/widget/dtd/widget-screen.xsd +@@ -526,6 +526,15 @@ under the License. + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/artifact/ArtifactInfoGatherer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/artifact/ArtifactInfoGatherer.java +index 0ea33934fd..44657bf1a1 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/artifact/ArtifactInfoGatherer.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/artifact/ArtifactInfoGatherer.java +@@ -101,6 +101,7 @@ import org.apache.ofbiz.widget.model.ModelScreenWidget.ScreenLink; + import org.apache.ofbiz.widget.model.ModelScreenWidget.Screenlet; + import org.apache.ofbiz.widget.model.ModelScreenWidget.Section; + import org.apache.ofbiz.widget.model.ModelScreenWidget.Tree; ++import org.apache.ofbiz.widget.model.ModelScreenWidget.VueJs; + import org.apache.ofbiz.widget.model.ModelSingleForm; + import org.apache.ofbiz.widget.model.ModelTree; + import org.apache.ofbiz.widget.model.ModelTree.ModelNode; +@@ -242,6 +243,10 @@ public final class ArtifactInfoGatherer implements ModelWidgetVisitor, ModelActi + } + + @Override ++ public void visit(VueJs vuejs) throws Exception { ++ } ++ ++ @Override + public void visit(Menu menu) throws Exception { + } + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java +index 36d4742d1a..7fbe500736 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java +@@ -37,6 +37,7 @@ import org.apache.ofbiz.base.util.cache.UtilCache; + import org.apache.ofbiz.base.util.collections.MapStack; + import org.apache.ofbiz.base.util.string.FlexibleStringExpander; + import org.apache.ofbiz.base.util.template.FreeMarkerWorker; ++import org.apache.ofbiz.widget.WidgetFactory; + import org.apache.ofbiz.widget.renderer.ScreenRenderer; + import org.apache.ofbiz.widget.renderer.ScreenStringRenderer; + import org.apache.ofbiz.widget.renderer.html.HtmlWidgetRenderer; +@@ -124,6 +125,8 @@ public class HtmlWidget extends ModelScreenWidget { + subWidgets.add(new HtmlTemplate(modelScreen, childElement)); + } else if ("html-template-decorator".equals(childElement.getNodeName())) { + subWidgets.add(new HtmlTemplateDecorator(modelScreen, childElement)); ++ } else if ("vuejs".equals(childElement.getNodeName())) { ++ subWidgets.add(WidgetFactory.getModelScreenWidget(modelScreen, childElement)); + } else { + throw new IllegalArgumentException("Tag not supported under the platform-specific -> html tag with name: " + + childElement.getNodeName()); +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java +index 372b253e47..0391733890 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java +@@ -1041,6 +1041,71 @@ public abstract class ModelScreenWidget extends ModelWidget { + return styleExdr; + } + } ++ public static final class VueJs extends ModelScreenWidget { ++ public static final String TAG_NAME = "vuejs"; ++ private final FlexibleStringExpander componentNameExdr; ++ private final List parameterList; ++ ++ public VueJs(ModelScreen modelScreen, Element vueJsElement) { ++ super(modelScreen, vueJsElement); ++ ++ this.componentNameExdr = FlexibleStringExpander.getInstance(vueJsElement.getAttribute("component-name")); ++ List parameterElementList = UtilXml.childElementList(vueJsElement, "parameter"); ++ if (parameterElementList.isEmpty() ) { ++ this.parameterList = Collections.emptyList(); ++ } else { ++ List parameterList = new ArrayList<>(parameterElementList.size()); ++ for (Element parameterElement : parameterElementList) { ++ parameterList.add(new Parameter(parameterElement)); ++ } ++ this.parameterList = Collections.unmodifiableList(parameterList); ++ } ++ ++ } ++ ++ @Override ++ public void renderWidgetString(Appendable writer, Map context, ScreenStringRenderer screenStringRenderer) { ++ try { ++ screenStringRenderer.renderVueJs(writer, context, this); ++ } catch (IOException e) { ++ String errMsg = "Error rendering vue-js in screen named [" + getModelScreen().getName() + "]: " + e.toString(); ++ Debug.logError(e, errMsg, MODULE); ++ throw new RuntimeException(errMsg); ++ } ++ } ++ ++ public Map getParameterMap(Map context) { ++ Map fullParameterMap = new HashMap<>(); ++ for (Parameter parameter : this.parameterList) { ++ Object retVal = null; ++ if (parameter.value != null) { ++ retVal = parameter.value.expandString(context); ++ } else if (parameter.fromField != null && parameter.fromField.get(context) != null) { ++ retVal = parameter.fromField.get(context); ++ } else { ++ retVal = context.get(parameter.name); ++ } ++ fullParameterMap.put(parameter.getName(), retVal); ++ } ++ return fullParameterMap; ++ } ++ ++ public String getComponentName(Map context) { ++ return this.componentNameExdr.expandString(context); ++ } ++ ++ public FlexibleStringExpander getComponentNameExdr() { ++ return componentNameExdr; ++ } ++ public List getParameterList() { ++ return parameterList; ++ } ++ ++ @Override ++ public void accept(ModelWidgetVisitor visitor) throws Exception { ++ visitor.visit(this); ++ } ++ } + + public static final class Form extends ModelScreenWidget { + public static final String TAG_NAME = "include-form"; +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelWidgetVisitor.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelWidgetVisitor.java +index f081f98314..d42b52efef 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelWidgetVisitor.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelWidgetVisitor.java +@@ -68,6 +68,8 @@ public interface ModelWidgetVisitor { + + void visit(ModelScreenWidget.Label label) throws Exception; + ++ void visit(ModelScreenWidget.VueJs vuejs) throws Exception; ++ + void visit(ModelScreenWidget.ScreenLink link) throws Exception; + + void visit(ModelScreenWidget.Menu menu) throws Exception; +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/XmlWidgetVisitor.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/XmlWidgetVisitor.java +index 0777a7058e..c7ecc9fa64 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/XmlWidgetVisitor.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/XmlWidgetVisitor.java +@@ -44,6 +44,7 @@ import org.apache.ofbiz.widget.model.ModelScreenWidget.ScreenLink; + import org.apache.ofbiz.widget.model.ModelScreenWidget.Screenlet; + import org.apache.ofbiz.widget.model.ModelScreenWidget.Section; + import org.apache.ofbiz.widget.model.ModelScreenWidget.Tree; ++import org.apache.ofbiz.widget.model.ModelScreenWidget.VueJs; + import org.apache.ofbiz.widget.model.ModelTree.ModelNode; + import org.apache.ofbiz.widget.model.ModelTree.ModelNode.ModelSubNode; + +@@ -230,6 +231,16 @@ public class XmlWidgetVisitor extends XmlAbstractWidgetVisitor implements ModelW + } + + @Override ++ public void visit(VueJs vuejs) throws Exception { ++ writer.append(""); ++ visitParameters(vuejs.getParameterList()); ++ writer.append(""); ++ } ++ ++ @Override + public void visit(Menu menu) throws Exception { + writer.append(" context, ModelScreenWidget.HorizontalSeparator separator) throws IOException; + public void renderLabel(Appendable writer, Map context, ModelScreenWidget.Label label) throws IOException; ++ public void renderVueJs(Appendable writer, Map context, ModelScreenWidget.VueJs vuejs) throws IOException; + public void renderLink(Appendable writer, Map context, ModelScreenWidget.ScreenLink link) throws IOException; + public void renderImage(Appendable writer, Map context, ModelScreenWidget.ScreenImage image) throws IOException; + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java +index 7cd5040ff2..36951637d0 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java +@@ -223,6 +223,10 @@ public class MacroScreenRenderer implements ScreenStringRenderer { + parameters.put("style", label.getStyle(context)); + executeMacro(writer, "renderLabel", parameters); + } ++ @Override ++ public void renderVueJs(Appendable writer, Map context, ModelScreenWidget.VueJs vuejs) throws IOException { ++ //TODO should be implemented ++ } + + @Override + public void renderHorizontalSeparator(Appendable writer, Map context, ModelScreenWidget.HorizontalSeparator separator) throws IOException { +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11769_0001-Add-icon-management-name-and-tooltips-abstraction.patch b/vuejs/ofbizCommit2add/OFBIZ-11769_0001-Add-icon-management-name-and-tooltips-abstraction.patch new file mode 100644 index 000000000..8e826d69e --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11769_0001-Add-icon-management-name-and-tooltips-abstraction.patch @@ -0,0 +1,103 @@ +From e9d0c2b32d47aed7c050e6c03c4d7e9fb80a5d56 Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 12:34:57 +0200 +Subject: [PATCH 21/25] Improved: Add icon management, name and tooltips abstraction (OFBIZ-11769) + +two properties files, to be able to use a logical name not a "path" in link, hyperlink, ... +--- + framework/common/config/CommonIconsTooltips.xml | 1 + + themes/common-theme/config/iconsPurpose.properties | 1 + + .../webapp/images/icons/famfamfam/application_addr.png | Bin 0 -> 567 bytes + .../webapp/images/icons/famfamfam/application_addy.png | Bin 0 -> 598 bytes + .../webapp/images/icons/famfamfam/link_go_inverse.png | Bin 0 -> 674 bytes + 5 files changed, 2 insertions(+) + create mode 120000 framework/common/config/CommonIconsTooltips.xml + create mode 120000 themes/common-theme/config/iconsPurpose.properties + create mode 100755 themes/common-theme/webapp/images/icons/famfamfam/application_addr.png + create mode 100755 themes/common-theme/webapp/images/icons/famfamfam/application_addy.png + create mode 100755 themes/common-theme/webapp/images/icons/famfamfam/link_go_inverse.png + +diff --git a/framework/common/config/CommonIconsTooltips.xml b/framework/common/config/CommonIconsTooltips.xml +new file mode 120000 +index 0000000000..14241ec0b3 +--- /dev/null ++++ b/framework/common/config/CommonIconsTooltips.xml +@@ -0,0 +1 @@ ++../../../plugins/vuejsPortal/ofbizFiles/framework/common/config/CommonIconsTooltips.xml +\ No newline at end of file +diff --git a/themes/common-theme/config/iconsPurpose.properties b/themes/common-theme/config/iconsPurpose.properties +new file mode 120000 +index 0000000000..965e398ad8 +--- /dev/null ++++ b/themes/common-theme/config/iconsPurpose.properties +@@ -0,0 +1 @@ ++../../../plugins/vuejsPortal/ofbizFiles/themes/common-theme/config/iconsPurpose.properties +\ No newline at end of file +diff --git a/themes/common-theme/webapp/images/icons/famfamfam/application_addr.png b/themes/common-theme/webapp/images/icons/famfamfam/application_addr.png +new file mode 100755 +index 0000000000000000000000000000000000000000..13d84faa60e072445995c482e011e5b12afa2365 +GIT binary patch +literal 567 +zcmV-70?7S|P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*q? +z3?3RMY{7v500Fm2L_t(I%cYV%YgADXhMzh2Zd_bfAuG{FM2H|L*lZ!hfSt9C7PdBG +z8Y_Q+pjBWYKfuC58-IeWV4fPw;pVf0SW-7jUzxIJRlGe!XxlTITcJ@oH^OY +z!!rOWZ!;Mtp=8K}g^`kBmN0Xy5Je=)CclrS1hf-r~>g&VY%1`iqa?}c)5C&&X+M#AiiVYw +z10-Q)Xdh^A@i;4AKLfBkSYojI5r8+RmOmx6UJAfk71q|tjAVv2dC04aLk2q^cz$UW +zfWzM>UDie~^sksK9FpKy!(9UH9O9f^=Pxt-e()2G7`vKO>GUq+kqk#Yb002ovPDHLk +FV1iHF>8k($ + +literal 0 +HcmV?d00001 + +diff --git a/themes/common-theme/webapp/images/icons/famfamfam/application_addy.png b/themes/common-theme/webapp/images/icons/famfamfam/application_addy.png +new file mode 100755 +index 0000000000000000000000000000000000000000..b400332ef0e188adef254dd3b033f21b9928afda +GIT binary patch +literal 598 +zcmV-c0;&CpP)Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RY2L%rmBY6Z_C;$Kf=t)FDR5;6} +zlCe%yVGxDC`R@gmz~Z7PBvzYXqQsI;jlKefT3BjBtgL(iO|&8*;T24@@fCam3xlFD +zx}jJDE5dF9yPLf`b1d$n*g>3Rl2iQo=A8Kl{SH{q#t93Gj|`_rjg-A#kH{@9l@myGdLI3~&07*qoM6N<$f?qiTIRF3v + +literal 0 +HcmV?d00001 + +diff --git a/themes/common-theme/webapp/images/icons/famfamfam/link_go_inverse.png b/themes/common-theme/webapp/images/icons/famfamfam/link_go_inverse.png +new file mode 100755 +index 0000000000000000000000000000000000000000..bf8412a1321b712fd2c19979d4e49ce7b4a2ae59 +GIT binary patch +literal 674 +zcmV;T0$u%yP)DanbL?Dk%dkKg(0hh$^=~B<-o>N`@*!aEzwNr;cC>RyM +zsDL-0pW(&L;+|tMw}e_>cu+dsHGtK|61KOuVAjiERD?(wx(^ATuHx=7%kh +z=w|05Nm%wO4tIt?K_E~80-=zZgj6cE*=#nQYPGs%+xCcQnt8)89KtzAZ|rz)=tiw@ +zva64rJ(9q}(j2NAC5VCmi84?@MBH^3?RI;kQmN3{+1Zs+sdTMaEPi1Cz^E9H7+32R +zcg}l2RSgZCE~7l#1+6o{f2)-+9yweE~B +ztY@Cetb>n@jgdeg0FTE5%d(J8r<;!Bj5v;SUDGsK6vgoF89C>3^?LnQG#Z_3ygv4} +z(P#{3gIU4v_d``xf=RTb@a +zyE!#AHE&r~XmoTm914X5MNzIyPfx=zjE97fEC7(5SB}TyN-P$;t!dhXs;a(nxm>Q* +zY6Aeey +Date: Wed, 27 May 2020 21:45:23 +0200 +Subject: [PATCH 22/22] Improved: Add watcherName as new field in + PortalPagePortlet entity (OFBIZ-11770) + +When a watcherName is associated to a Portal - Portlet, it's used to +created a container (with this watcher) "around" the portlet, and so be +able to update it with a set-area, refresh-area, set-watcher, +refresh-watcher +--- + framework/common/entitydef/entitymodel.xml | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/framework/common/entitydef/entitymodel.xml b/framework/common/entitydef/entitymodel.xml +index 490a9410ef..7d8b51ef4b 100644 +--- a/framework/common/entitydef/entitymodel.xml ++++ b/framework/common/entitydef/entitymodel.xml +@@ -827,6 +827,7 @@ under the License. + Identify the portalPortlet instance in case more copy of the same portalPortlet are present in the same portalPage + + ++ + + + +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11771_0001-add-auto-parameters-portlet-as-children-of-event-upd.patch b/vuejs/ofbizCommit2add/OFBIZ-11771_0001-add-auto-parameters-portlet-as-children-of-event-upd.patch new file mode 100644 index 000000000..c08eb7175 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11771_0001-add-auto-parameters-portlet-as-children-of-event-upd.patch @@ -0,0 +1,127 @@ +From 606251ffe8c2b99c34d6bda1ecf1f09f569077b7 Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 12:12:47 +0200 +Subject: [PATCH 20/25] Improved: add auto-parameters-portlet as children of (OFBIZ-11771) + event-update-area (field and form). + +auto-parameters-portlet add for parameters in parameters-map: +- portalPageId +- portalPortletId +- portletSeqId +- currentAreaId +auto-parameters-portlet is used by showPortlet to be able to renderer +only one portlet, because often, it's necessary to have the 4 parameters +for a future other update (ex: button back) +--- + framework/widget/dtd/widget-common.xsd | 8 ++++++++ + framework/widget/dtd/widget-form.xsd | 2 ++ + .../org/apache/ofbiz/widget/model/CommonWidgetModels.java | 12 ++++++++++-- + .../main/java/org/apache/ofbiz/widget/model/ModelForm.java | 12 ++++++++++-- + 4 files changed, 30 insertions(+), 4 deletions(-) + +diff --git a/framework/widget/dtd/widget-common.xsd b/framework/widget/dtd/widget-common.xsd +index 0fd32d3334..4a076f2ed3 100644 +--- a/framework/widget/dtd/widget-common.xsd ++++ b/framework/widget/dtd/widget-common.xsd +@@ -515,6 +515,13 @@ under the License. + + + ++ ++ ++ Generate 4 parameters : portalPageId, portalPortletId, portletSeqId and currentAreaId update from-field parameters.xxx ++ ++ ++ ++ + + + +@@ -525,6 +532,7 @@ under the License. + + + ++ + + + +diff --git a/framework/widget/dtd/widget-form.xsd b/framework/widget/dtd/widget-form.xsd +index f82a0b2f44..ec3096dc58 100644 +--- a/framework/widget/dtd/widget-form.xsd ++++ b/framework/widget/dtd/widget-form.xsd +@@ -587,6 +587,7 @@ under the License. + + + ++ + + + +@@ -1553,6 +1554,7 @@ under the License. + + + ++ + + + +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/CommonWidgetModels.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/CommonWidgetModels.java +index e339858201..1cbcc1f9aa 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/CommonWidgetModels.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/CommonWidgetModels.java +@@ -364,14 +364,22 @@ public final class CommonWidgetModels { + this.linkType = linkElement.getAttribute("link-type"); + } + List parameterElementList = UtilXml.childElementList(linkElement, "parameter"); +- if (parameterElementList.isEmpty()) { ++ boolean autoPortletParamsElement = UtilXml.firstChildElement(linkElement, "auto-parameters-portlet") == null ? false : true; ++ if (parameterElementList.isEmpty() && ! autoPortletParamsElement) { + this.parameterList = Collections.emptyList(); + } else { ++ int paramListSize = parameterElementList.size() + (autoPortletParamsElement ? 4 : 0); + List parameterList = new ArrayList<>( +- parameterElementList.size()); ++ paramListSize); + for (Element parameterElement : parameterElementList) { + parameterList.add(new Parameter(parameterElement)); + } ++ if (autoPortletParamsElement) { ++ parameterList.add(new CommonWidgetModels.Parameter("portalPageId", "parameters.portalPageId", true)); ++ parameterList.add(new CommonWidgetModels.Parameter("portalPortletId", "parameters.portalPortletId", true)); ++ parameterList.add(new CommonWidgetModels.Parameter("portletSeqId", "parameters.portletSeqId", true)); ++ parameterList.add(new CommonWidgetModels.Parameter("currentAreaId", "parameters.currentAreaId", true)); ++ } + this.parameterList = Collections.unmodifiableList(parameterList); + } + Element autoServiceParamsElement = UtilXml.firstChildElement(linkElement, "auto-parameters-service"); +diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +index 3a9e0a4a49..f94a742eb6 100644 +--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java ++++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java +@@ -1750,13 +1750,21 @@ public abstract class ModelForm extends ModelWidget { + this.defaultServiceName = defaultServiceName; + this.defaultEntityName = defaultEntityName; + List parameterElementList = UtilXml.childElementList(updateAreaElement, "parameter"); +- if (parameterElementList.isEmpty()) { ++ boolean autoPortletParamsElement = UtilXml.firstChildElement(updateAreaElement, "auto-parameters-portlet") == null ? false : true; ++ if (parameterElementList.isEmpty() && ! autoPortletParamsElement) { + this.parameterList = Collections.emptyList(); + } else { +- List parameterList = new ArrayList<>(parameterElementList.size()); ++ int paramListSize = parameterElementList.size() + (autoPortletParamsElement ? 4 : 0); ++ List parameterList = new ArrayList<>(paramListSize); + for (Element parameterElement : parameterElementList) { + parameterList.add(new CommonWidgetModels.Parameter(parameterElement)); + } ++ if (autoPortletParamsElement) { ++ parameterList.add(new CommonWidgetModels.Parameter("portalPageId", "parameters.portalPageId", true)); ++ parameterList.add(new CommonWidgetModels.Parameter("portalPortletId", "parameters.portalPortletId", true)); ++ parameterList.add(new CommonWidgetModels.Parameter("portletSeqId", "parameters.portletSeqId", true)); ++ parameterList.add(new CommonWidgetModels.Parameter("currentAreaId", "parameters.currentAreaId", true)); ++ } + this.parameterList = Collections.unmodifiableList(parameterList); + } + Element autoServiceParamsElement = UtilXml.firstChildElement(updateAreaElement, "auto-parameters-service"); +-- +2.11.0 + diff --git a/vuejs/ofbizCommit2add/OFBIZ-11999_0024-Add-Label-for-example-portal-page-ExampleMgmtFrontJs.patch b/vuejs/ofbizCommit2add/OFBIZ-11999_0024-Add-Label-for-example-portal-page-ExampleMgmtFrontJs.patch new file mode 100644 index 000000000..e5eb0fbd6 --- /dev/null +++ b/vuejs/ofbizCommit2add/OFBIZ-11999_0024-Add-Label-for-example-portal-page-ExampleMgmtFrontJs.patch @@ -0,0 +1,32 @@ +From e0408fd23fe0088754f51b49939a666f8958dee5 Mon Sep 17 00:00:00 2001 +From: holivier +Date: Wed, 27 May 2020 14:59:49 +0200 +Subject: [PATCH 24/25] Add Label for example portal page ExampleMgmtFrontJs + and ExampleRecapJs in CommonPortalEntityLabels + +--- + framework/common/config/CommonPortalEntityLabels.xml | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/framework/common/config/CommonPortalEntityLabels.xml b/framework/common/config/CommonPortalEntityLabels.xml +index bd65f5164d..48670ed21c 100644 +--- a/framework/common/config/CommonPortalEntityLabels.xml ++++ b/framework/common/config/CommonPortalEntityLabels.xml +@@ -123,4 +123,14 @@ + 仓储设施 + 場所 + ++ ++ ++ One example (RecapPageFrontJs) ++ Un exemple (RecapPageFrontJs) ++ ++ ++ Examples management (MgmtPageFrontJs) ++ Gestion des exemples (MgmtPageFrontJs) ++ ++ + +-- +2.11.0 + diff --git a/vuejs/src/docs/asciidoc/FrontJsVueJs_en.adoc b/vuejs/src/docs/asciidoc/FrontJsVueJs_en.adoc new file mode 100644 index 000000000..fd897455b --- /dev/null +++ b/vuejs/src/docs/asciidoc/FrontJsVueJs_en.adoc @@ -0,0 +1,89 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += FrontJs Portal + +frontJsPortal component included in frontJsPortal plugin aim to handle user interface with modern javascript framework +(Vue.Js, React, Angular, ect...). + +It use portlet/portal system defined by XML and a javascript application. + +This component is actually at POC (Proof Of Concept) state, so it aim to show some concrete ideas in order to discuss about them. + +It is produced in an "Agility" spirit which mean that it focus to result in despite of some technical debt. + +This documentation aim to, + +* Explain the current state of the component, how it work and what points had been choose or took away. +* Prepare future documentation by explaining element meant for future users. + +The two main goal of using a javascript framework for user interface is + +1. to have a "modern" look and feel + * using recent GUI library + * usable in multiple context (PC, tablet, smartphone) +1. to increase interactive elements in screen, even if theses screens are based on standard modules : + * update some part of the screen by action or by data update; + * modify forms field as function of other field values; + * simpler screen configuration (portal page) without having to worry about portlets interaction. + +include::_include/poc-vuejs_en.adoc[leveloffset=+1] + +== modular and generic UI +Aiming to have an open and modular ERP, it's important that the user interface configuration allow to manage multiple screens +from a set of screen "module" for manage *same business object* type in *different business context*. + +Next chapters describe a user interface management system's POC aiming to respond to theses modularity needs. + +include::Ui-POC.adoc[leveloffset=+2] + + +== Portlet +Portlet is an autonomous part of the screen, which mean that there can be action that only alter this part of the screen, +mainly fired by itself. + +Portlet allow a huge modularity gain in user interface, that way an user action (click on a link, type in a field, ect...) +must not precise the portlet itself but a logical name which can be subscribed one or more portlet. + +This logical name which is subscribed by portlets is called `watcherName`, this field is in the association table between +PortalPage and PortalPortlet. + +=== Portlet update +It's the update of the data stored in js client side store who trigger portlet render update. + + +== Portal Page + + +== FrontJs Glossary + +[#WATCHERS] +watchers:: They are objects stored in js store that can be altered remotely. watcher's subscribers update themselves with the +new value when it change. + +watchers:: c'est nom du store VueJs utilisé pour stocker les différentes variable sur lequel les container ou portletsont abonné +et donc se mette à jours quand celui-ci change. + +[#WATCHERNAME] +watcherName:: It is the key of a watcher + + + + + + + diff --git a/vuejs/src/docs/asciidoc/Ui-POC.adoc b/vuejs/src/docs/asciidoc/Ui-POC.adoc new file mode 100644 index 000000000..f57a87803 --- /dev/null +++ b/vuejs/src/docs/asciidoc/Ui-POC.adoc @@ -0,0 +1,515 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// + += Use cases for POC-UI + +Theses use cases are to be used for new UI POC, documentation associated +and selenium unit task test. + +All these use cases should be done with existing entities and services, +if it's necessary to develop one, simplify the use case, the goal is UI, +not service or entity. + +These use case description are done in a agile philosophy, only main +point is present and during realization details choices will be discuss +and done. + +== Preliminary remarks : +. In this document, the term "application" corresponding to + "plugin component" in the ofbiz terminology which is not same as + a "applications/trunk component" in ofbiz terminology. An application is + dedicated for a business purpose for a user type, it's build by + assembling piece of ofbiz components, sometimes without any specifics + entities, services and screen (ex: CRM-B2C, CRM-B2B, SFA are 3 + applications uses by sales men) + +. Each use case is on an "application" and is part of one of the menu + of this application. ++ +-- +Of course, this document describe only menu-option + needed by the use-case. As it's difficult to do a clear definition of + "web-page" because it's depending of theme/template, use case is for me + at two level : + +.. screen, which can be well define +.. page, which depend on default theme + +Of course, some of use case (screen or page) will be done by a previous + one (ex : sometime edit is done by the same screen as add). It's even, + one of the re-usable important point (UI, Doc and Selenium) +-- +. Each time a line (or point) start by a "?" the question is "is this +point is needed ?" + += Release and goal + +Goal is the POC realization, not doing multiple super applications. POC +realization should generate discussion and decision, each time be +careful to mainly discuss on UI, Doc or Selenium and not on use case +business justification. Use case are to be realistic but mainly as a +support for a specifics UI, Doc or Selenium needed. + +== V1 +**Main Goal is to proof the technical points**. Of course UI +re-organization is the driver for this phase, documentation start to +work in parallel but will wait first realization to test integration for +help. For selenium unit task, it will be a simple usage of the new UI +with the default theme, the sole purpose being to check that test with +default theme is simple. + +So never mind if all cases is not done, the list exist to give different +classic case what we will need to know how to process it. Some case +which seem to be duplicate from other can be use for beginner who want +to help and check if the concept is understanding. + +During V1 realization, UseCase list will be updated + +== V2 +*Main Goal is to check if* the solution is *useful for the different +type of contributors* + +* experiment developer +* beginner developer +* functional consultant for parameters +* functional consultant for personalization +* ? end user - for parameters ? (if it's still possible with the new +architecture) + +Use Case list will be use as a deployment plan. The list contains +similar case to these which are realize in V1, so those on V2 can be +achieve by all types of contributors. + +== Documentation Goal +* Apache OFBiz User documentation (asscidoc) +* Apache OFBiz web site wiki +* OFBiz Help + + +== Selenium test +. demo data for each use case and scenario +. selenium scenario test for each page (or page group) +. selenium unit test for each screen + + += Simple Party Management + +== Which Sub-Application +. in HR application, simple employee management +. in CRM B2C application, simple customer (person) management +. in eCommerce, simple profile page +. in HR application, simple organization/place (group) management +. in CRM B2B application, simple customer (company) management +. in Facility application, simple worker management + +== Which Party sub-component +. Party - Person - PartyGroup +. Contact Mech, + * with postal address, phone and mail; + * one or two fixes purpose (ex: phone fix number and mobile phone number) +. Role +. Party Identification +. Party association +. Not [line-through]*UserLogin* because all Security entities should + be use and it will generate a too large domain for this POC + +== Which Screen +. Party + * find, list + * A person + ** add / edit, show + * A group + ** add / edit, show + * A company + ** add / edit, show + * show a synthesis view (party/person/company, contact informations, roles, Identification) + ** Person + ** Company + ** PartyGroup + +. Contact information + * all contact informations (for one party / facility) + ** with and without purpose + ** with and without history + ** deactivated + * add / edit postal address + * add / edit mail + * add / edit phone + +. Role + * list for a party + * add a role (for a parent RoleType) + * add a role in two step : + . select parent RoleType + . select the role + * remove a role + +. Party Identifications + * list, add, remove + += HR Employee management +In HR Component, starting person management with the more complete form about person. + +* Menu option to manage employee + ** find, list, show, add, edit and manage his + *** contact information + *** identification (3 idTypes, one mandatory, two optionals) +* template page with a header (or sidebar or ...) to show on which employee we are + +== Use Case Screen : +. find Person + * simple form (only on party or person) + * with an add button (which can be show or not depending on parameter or authorization) +. Person list with an add button (which can be show or not depending on parameter or authorization) +. add a Person +. show a Person +. show a Person with sub-menu with two options : contact informations and Identifications +. edit a Person +. List of all contact informations for a person, with an add button + (which can be show or not depending on parameter or authorization) +. add a postal address +. add a mail +. add a phone number (to go step by step, without purpose management, + will be done in next Use Case group) +. edit a postal address +. edit a mail +. edit a phone number +. List of all identification number for a person, with an add button + (which can be show or not depending on parameter or authorization) +. add a identification number with choice of identification type +. edit a identification number with choice of identification type +. add a identification number with a fix identification type +. edit a identification number with a fix identification type + +== Use Case Page : +. create a person +. search a person +. visualize a person +. manage informations about a person +. template page with a header (or sidebar or ...) to show on which + employee we are, (for example to show all his knowledges, or his skills, + or his positions, or his ...) +. manage informations about a person on one page, and with access at + this page directly by a field (auto-completion on id, first, last name) + += CRM B2C, customer mgnt +In a CRM B2C application, the customer (so, in this context, a person) management. + +The difference from previous use case group is : + +. person form is more simple than in HR +. role will be used to characterize customer position (suspect, + prospect, with_Quote, customer) + +Menu option to manage employee + +* find (with role field), list, show, add, edit and manage his + ** contact informations + ** identification (3idTypes, one mandatory, two optionals) +* template page with a header (or sidebar or ...) to show on which customer we are + +== Use Case Screen : +. find Person with an add button (which can be show or not depending on parameter or authorization) + * search field same as in HR find person + * role field which can appear or not, when not appear a fix value has been put as parameters. + * contact information field, phone, mail, town. These fields can be show + or not by the user with a "deploy" button + +. Person list with an add button (which can be show or not depending on parameter or authorization) + * role field appear or not, when not appear a fix value has been put as + parameters, so only person with this role appear + +. add a Person, all main informations in the form + * role + * less field about person than in HR form + * 1 postal address + * 2 phone number + * 1 identification number + +. show a Person, all main informations in the screen with indicator for + contact information and identification when there are more data that + what it's show. + +. show a Person with sub-menu with options : + * contact informations + * Identifications + * role history + * change role : a direct action button + +. edit a Person, only "Person" field + +. a button bar to change role (ex: for a suspect, there are the 3 + options), this use case is for having a action bar, in this business + process case it's maybe not a need, but for more complex object like + order or task, it's a classical need. + +. List of all contact informations for a person, with one or multiple + add buttons (which can be show or not depending on parameter or + authorization) and purpose are show, it's the second step, with purpose + management. + +. add a postal address (or just a purpose) +. add a mail +. add a phone number +. edit a postal address +. edit a mail +. edit a phone number +. List of all identification number for a person, with an add button + (which can be show or not depending on parameter or authorization) +. add a identification number with choice of identification type +. edit a identification number with choice of identification type + + +== Use Case Page +. create a new entry in CRM (role is choose during creation) +. search a "customer" (or suspect, prospect, ...) +. visualize a "customer" +. manage informations about a "customer" +. template page with a header (or sidebar or ...) to show on which + "customer" we are, (for example to show all his quotes, or his orders, or ...) +. manage informations about a person on one page, and with access at + this page directly by a field (auto-completion on id, first, last name) + + += eCommerce, profile page +A simple profile page. + +The difference from previous use case will be mainly on Use Case Page +because eCommerce theme could be more original and public user interface +should be, most of the time, more simple. + +== Use Case Screen : +. show the person, all main informations in the screen with indicator + for contact information and identification when there are more data that + what it's show. + +. show the Person with sub-menu with options : + * contact informations + * Identifications + +. edit a Person, only "Person" field +. List of all contact informations for a person, with an add button and + purpose are show, purpose is need for invoice or shipping. +. add a postal address (or just a purpose) +. add a mail +. add a phone number +. edit a postal address +. edit a mail +. edit a phone number + +== Use Case Page : +. visualize the profile (the person) with edit button +. manage his contact informations +. manage his identifications +. All in one page, which can be look as a long page. + + += HR organization mgnt +In HR component, a simple organization/place (group) management. + +Now PartyGroup management (very simple), but with complex screen to +manage hierarchy. In this use case group we will use the word "group" +for service or department, or subsiadiry. + +* Menu option to manage the Company organization + ** manage group + ** associated employee in a group + ** manage a hierarchy of group + +== Use Case Screen : +. find group (with a specific partyType) + * simple form (only on party or partyGroup) + * with an add button (which can be show or not depending on parameter orauthorization) + +. PartyGroup list with an add button (which can be show or not dependingon parameter or authorization) +. add a group +. show a Person, all informations in screen with sub-menu with two + options : contact informations and Identifications +. edit a Group +. List all contact informations for a group, with an add button (which + can be show or not depending on parameter or authorization) +. add a postal address +. add a phone number +. edit a postal address +. edit a phone number +. List all identification number for a group, with an add button (which + can be show or not depending on parameter or authorization) +. add a identification number with choice of identification type +. edit a identification number with choice of identification type +. add a identification number with a fix identification type +. edit a identification number with a fix identification type +. List all person associated to the group with two add buttons (which + can be, individually, show or not depending on parameter or + authorization) + * add a manager + * add a member +. List all group associated to the group (the child) with two add + buttons (which can be, individually, show or not depending on parameter + or authorization) + * add an existing group as a child + * create a new group and add it as a child + * in the list, each group is a link to this screen, to be able to navigate top-down + * a third button to go to the parent level, to be able to navigate bottom-up + * the name of the group manager appear above the list +. ? List all parent group for a group or for a person ? +. show group hierarchy as a tree with action or detail at each level, + top-down +. show group hierarchy as a tree with action or detail at each level, + bottom-up + +== Use Case Page : +. search a group +. manage a group +. manage its contact informations +. manage hierarchy step by step (parent to child or child to parent) +. manage hierarchy with a tree view +. in HR employee, show the tree, top-down or bottom-up with the template "for an employee" + + += CRM B2B customer mgnt +In a CRM B2B application, the customer (so, in this context, a company) management. + +For clarification, in these Use Cases, B2B is an other application than B2C. + +The "CRM B2C & B2B" will be a third, but not in this list because +it contains no specificity on screen-page definition + +The main difference between B2C is : + +. company versus person, +. contact management with PartyAssociation +. ? customer organization management ? + +== Use Case Screen : +. find customer (a company (specific partyType)) with an add button + (which can be show or not depending on parameter or authorization) + * search field are on multiple entities with some part deploy or not + * role field which can appear or not, when not appear a fix value has been put as parameters. + * contact information field, phone, mail, town. These fields can be show + or not by the user with a "deploy" button + +. Company list with an add button (which can be show or not depending on parameter or authorization) + * role field appear or not, when not appear a fix value has been put as + parameters, so only company with this role appear + +. add a Company, all main informations in the form + * role + * field from PartyGroup + * 1 postal address + * 2 phone number + * 2 identification number + +. show a Company, all main informations in the screen with indicator for + contact informations and identification when there are more data that + what it's show. + +. show a Company with sub-menu with options : + * contact informations + * Identifications + * role history + * change role : a direct action button + +. edit a Company, only "Company" field + +. a button bar to change role (ex: for a suspect, there are the 3 + options), this use case is for having a action bar. + + In this business process case it's maybe not a need, but for more complex object like + order or task, it's a classical need. + +. List of all contact informations for a company, with an add button + (which can be show or not depending on parameter or authorization) and + purpose are show, (so, with purpose management). + +. add a postal address (or just a purpose) +. add a mail +. add a phone number with purpose +. edit a postal address +. edit a mail +. edit a phone number +. List of all identification number for a company, with an add button + (which can be show or not depending on parameter or authorization) +. add a identification number with choice of identification type +. edit a identification number with choice of identification type + +. list of contact (person) associated to this company with an add button +(which can be show or not depending on parameter or authorization) + * a contact is a person with contact information + * list with only one line per contact + * list of block with contact details for each + +. edit a contact or his contact information + +== Use Case Page : +Exactly the same as the CRMB2C + +. create a new entry in CRM (role is choose during creation) +. search a "customer" (or suspect, prospect, ...) +. visualize a "customer" +. manage informations about a "customer" +. template page with a header (or sidebar or ...) to show on which + "customer" we are, (for example to show all his quotes, or his orders, or ...) +. manage informations about a company on one page, and with access at + this page directly by a field (auto-completion on id, first, last name). + + += Facility worker mgnt +In Facility application, simple facility's worker management. + +For this last use case group, it's a simplification of the previous one. + +Only a very simple and short process for adding people. + +It's the last one, because the goal is to check if it's easy and rapid +to create (or parametrize) a new small application from existing one. + +In the Warehouse Management application (simple version OOTB) + +* in the administration menu + ** the user menu to manage internal user per facility In the standard + business process, it will be used mainly for login and authorization, in + our case we will only manage person, his phone number and his facility + (where he's authorized) + ** the facility menu to manage contact informations and person authorized + + +== Use Case Screen : + +=== Already existing screen used +. find Person + * simple form (only on party or person) + * with an add button + +. Person list with an add button +. add a Person, simple form 3-6 fields +. show a Person +. show a Person with sub-menu with option to manage contact informations +. edit a Person +. List of all contact informations for a person, with one or multiple add button +. add a mail +. add a phone number +. edit a mail +. edit a phone number + +=== New Screen +. add a facility, simple form, if service exist, including some contact informations +. List of all existing facility +. List of all contact informations for a facility, with one or multiple add button +. List of all persons associated to the facility, with two add button + * add an existing person + * create a new person and add it to the facility +. List of all facility associated to a person, with one add button + * add an existing facility + +== Use Case Page : +. manage facilities +. manage persons +. visualize a facility details (info, contact informations, persons associated) diff --git a/vuejs/src/docs/asciidoc/_include/poc-situation_en.adoc b/vuejs/src/docs/asciidoc/_include/poc-situation_en.adoc new file mode 100644 index 000000000..5e4096671 --- /dev/null +++ b/vuejs/src/docs/asciidoc/_include/poc-situation_en.adoc @@ -0,0 +1,43 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += POC current situation +Currently VueJs POC is only able to manage PortalPage, but it will be simple to manage all other screen types. + +The Objective here is to make different use cases portal page to work. + +At the menu, there are different pages, actually the name of portal pages name are MgmtPageFrontJs and RecapPageFrontJs. + +The first page aim to find and edit examples. +The second page aim to show all properties of a given example. + +For each portlet present in theses pages, a portlet component is created in Vue.js with the portlet name which initialise itself +with showPortletFj. + +ShowPortletFj give the component all information about components it have to render (viewScreen) and data set it have to work with (viewEntities). +Actually ShowPortletFj respond with a JSON. + +image::PortalPageDetail-Example.svg[Imbrication des container] + +== ViewScreen and ViewEntities +Actually FrontJsViewHandler send all information to component who need update. +In a client side logic, ViewScreen would be send only the first time while the portlet call information then would only care about +ViewEntity which update the data set it work on. + +Actually "use-when" are handled by ofbiz in model model management whatever level they belong to (screen, form, field, menu), +so we can't get this information in the renderer. +Later, we had to transmit to renderer all screen elements and if there was "use-when" for manage them in the frontend. +This way next call could sent "use-when" correct value to properly update the final screen. \ No newline at end of file diff --git a/vuejs/src/docs/asciidoc/_include/poc-todo_en.adoc b/vuejs/src/docs/asciidoc/_include/poc-todo_en.adoc new file mode 100644 index 000000000..f67657eb6 --- /dev/null +++ b/vuejs/src/docs/asciidoc/_include/poc-todo_en.adoc @@ -0,0 +1,73 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += POC's remaining todos +This list contain only points which are already meet in use case process. + +Most of time, each new use case (or portlet) give new task (ex: new field type not yet meet, or field properties not yet process) + +== Minors + +* review and change field-find to include title in the field to remove the "title" coloumn in find form +* Manage the ConfMod in the showPortalPage, to show (and manage) the portlet parameters page. +* do a uri to send label for login screen, to be able to have translation in login screen +* Include header and footer in the VueJs application to have a full page manage by VueJs, and so have a complete Vue.Js look. +* Include all Common and Commonext part, like systemInfoNote + +== Majeurs + +* use VuejsRouter to be able to manage all screenType +* showPortletFj* must verify *security* about field SecurityServiceName and SecurityMainAction. + +== PortletWidget migration +Start of documentation for all portletwidget migration + +1. create/copy the screen in {component}PortletScreen and remove + + + + with +[source,XML] + + + + + + + + + + + + + + diff --git a/vuejs/src/docs/asciidoc/_include/poc-useCase_en.adoc b/vuejs/src/docs/asciidoc/_include/poc-useCase_en.adoc new file mode 100644 index 000000000..ddbafc263 --- /dev/null +++ b/vuejs/src/docs/asciidoc/_include/poc-useCase_en.adoc @@ -0,0 +1,28 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += POC's use cases + +== Operational +All example management by the two portal page ExampleMgmt et ExampleRecap. + +On the find, the sub list are not yet manage. + +== Next step +Party component will the next step whith most of its functions + + diff --git a/vuejs/src/docs/asciidoc/_include/poc-vuejs-details_en.adoc b/vuejs/src/docs/asciidoc/_include/poc-vuejs-details_en.adoc new file mode 100644 index 000000000..5b8043116 --- /dev/null +++ b/vuejs/src/docs/asciidoc/_include/poc-vuejs-details_en.adoc @@ -0,0 +1,87 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Vue.js + +== Vue.js general +todo + +== Components +A Vue.Js component is defined by 3 distinct blocks ( template, script, style ) which are regrouped in a single '.vue' file. + +. Template : +* Template must be contained in a single root element (div, span, table, ect... ) + +* In template we can access computed properties and function with double curly bracket *{{}}* + +* Classic html attributes can be preceded by ':' to use script properties and js code instead of plain text +* Vue.Js give us some directives (v-for, v-if v-on:click, ect...) that are bind to script context and work like a classic +attribute preceded by ':' and so can bind to script part and interpret js. + +. Script +* Script section is wrapped into an `export default {}` that contain the whole script elements. +TIP: import have to be made before this export +* This export is a map that can contain predefined keys used by Vue.Js (data, computed, methods, props, created, ect...) +** data() is a function that contain variable used is the component context. + +All variable contained in data() are reactive, which mean that Vue.Js will track any change on them and will reverberate +theses changes everywhere there are used to reevaluate render. +** computed is a map that contain evaluation based on reactive data. + +They can be used like a reactive variable in the component and will reevaluate itself when one of its entry had changed. +** methods is a map containing helper function (handleClick, doPost, ect...). + +WARNING: these functions don't aim to be reactive. +** props is an array of string who give attribute to the component for passing data from its parent to it. +** This section can also contain *hooks* of the component life cycle (created(), mount(), beforeUpdate(), ect...). + +Theses block of code will be executed when the hook is fired. + +See below : + +image:https://fr.vuejs.org/images/lifecycle.png["Component lifecycle", link="https://fr.vuejs.org/images/lifecycle.png"] + + + +. Style + +This part is dedicated to internal css of the component. + +Style of this part is applied before other style of the project. + +== Vuex +Vuex is the centralised state system of the application. +It allow us to create 'store' which contain data that can be accessed/modified by any component of the application. + +A store is composed of 4 elements : + +. `State` + +State is a map that contain all stored informations. + +State can only be modify by `mutation`. +Default state's data will be reactive. + +If Data had to be added in the state after its creation, they must be added with the method `Vue.set()` for them to be reactive. + +. `Mutations` + +Mutations is a map that contain function allowed to alter state. +By convention, mutation's key have to be upper-case. +Mutations can't be accessed through code, they must be called by actions. +Mutations must be synchronous. + +. `Actions` + +Mutation first aim to fire mutation. + +Actions can be asynchronous, in which case there return a promise. + +. `Getters` + +Getters allow to access state data. + +Getters are reactive. + +Getters can use parameters, if they are they return a function instead of a reactive variable. + + +Store can be split into modules who can be described in an index.js file, stores can so be stored into a modules repository. + + + +== Reactivity +todo \ No newline at end of file diff --git a/vuejs/src/docs/asciidoc/_include/poc-vuejs_en.adoc b/vuejs/src/docs/asciidoc/_include/poc-vuejs_en.adoc new file mode 100644 index 000000000..73a1ef721 --- /dev/null +++ b/vuejs/src/docs/asciidoc/_include/poc-vuejs_en.adoc @@ -0,0 +1,318 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += POC Vuejs Renderer + +Actually the first javascript application enabling to use portlet system - portal Apache OFBiz is written with the Vue.js framework. + +Vue.js has been choose for its easy learning curve and its community driven development instead of a corporation. + +In Vue.js, library vuetify has been choosen, because it's base on material design and have e very active community, so seem +to be the most used vue.js GUI libraries. + +== POC Vuejs Renderer installation + +. From a standard trunk ofbiz-framework with 205914a1beb commit or superior (that can be downloaded at +https://ofbiz.apache.org/source-repositories.html[documentation standard SourceRepository] using git). +. Create the 'plugins' repository at ofbiz-framework root folder. +. In the Apache OFBiz plugins repository, download the next repository using git : +.. *example plugin* at http://svn.apache.org/repos/asf/ofbiz/ofbiz-plugins/trunk/example +. In the ofbizextra plugins repository, download the 3 next repositories using git : +.. *vuejsportal plugin* that can be downloaded at https://gitlab.ofbizextra.org/ofbizextra/ofbizplugins/vuejsPortal + all vuejs components, a new ScreenViewHandler and 4 new renderers (screen, form, menu, tree) and all common files to all + fjs component. +.. *examplefjs plugin* that can be downloaded at https://gitlab.ofbizextra.org/ofbizextra/ofbizplugins/examplefjs.git + specifics files for using vue.js with ofbiz example component +.. *flatgreyfjs plugin* that can be downloaded at https://gitlab.ofbizextra.org/ofbizextra/ofbizplugins/flatgreyfjs.git + dedicated theme for vue.js with vuetify library +. In the ofbizextra git repository download ofbizJiraPatchAvailable project + + If there are some files, some patch may be necessary for this POC, for each patch you can goto JIRA + to have more explanation for each. + + To apply them it is recommended to create a branch on your ofbiz, and apply the patchs with git command + `git am patchName`. Currently these patchs are necessary: + .. OFBIZ-11645_0001-simple-methods-optional-in-compound-file.patch <= compound-widget files are used in POC + .. OFBIZ-11676_0001-Fixed-drop-down-field-not-work-when-in-a-compound-wi.patch <= compound-widget files are used in POC + .. OFBIZ-11708_0001-fixed-sort-order-field-not-works-in-CompoundWidgets.patch <= compound-widget files are used in POC +. If there are some files in the ofbizCommit2add, it's some patch waiting jira creation or dedicated for this POC, + so it's needed to apply them too + with `git am fileName` at the ofbiz root directory (replace fileName by the patch file name and path) +. After that you have to modify some files of ofbiz-framework (ex: model.java because some tag are added). + + For that, a (bash) script exist, it's located in the 'plugins/vuejs' : + + `./tools/applyOfbizFilesPatchs.sh` + + And then copy some files + + `rsync -a --exclude-from=./ofbizFiles/rsyncExcludes ./ofbizFiles/ ../../` + +. To be able to build the vue.js application you must have Node.js installed on your system. +.. To install Node.js on a debian based system, execute the following commands : + + `curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -` + + `sudo apt-get install -y nodejs` + + In case of another system, you can consult https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions +.. Meanwhile an integrated gradle command +... Being located at 'plugins/vuejs/webapp/vuejs' +* `npm i` <= to install the project dependencies +* `npm run build-prod` <= to build the project (it's possible to use build-dev to be able to use browser dev-tools) +. You can now launch OFBiz as regular : + +`./gradlew cleanAll loadAll ofbiz` + +(being located at the *root of ofbiz-framework* ) +. You can now connect to the application portal at https://localhost:8443/examplefjs/control/main + +== POC choices + +Apache OFBiz *trunk branch* usage. + +Principe and architecture of Apache OFBiz *Portal Page and Porlet* usage. + +The whole use cases are applied *to example component* and during the POC some new files used which should to be located in common +have been moved to examplefjs aiming to simplify the installation and update process. + +With examplefjs, there is an *additional webapp (examplefjs)*, it centralize allowed URIs for vuejs component, +currently this applicationName is listed in constantes.js file. + +vuejs component is not bound to a component, it just need a base URI for send its requests. +This uri is temporary set in constantes.js but it will be more flexible later aiming to use vuejs with multiple components + +Portal component ( https://localhost:8443/examplefjs/control/login ) use *ofbiz standard login by cookies* mechanism, it will +later use ofbiz login by token (cf. https://issues.apache.org/jira/browse/OFBIZ-9833). + +*screens, forms, menus* used for portlets are defined *with xml*, and dedicated files for better readability. + +There is *a component (vuejs) for each ofbiz screen element* ( SingleItemRow => vue.single-item-row ), which are defined by +renderer for screen, form and menu ( and for html renderer that correspond globally to macro.ftl ). Actually, for the POC, +to list them, you can search in vuejs existing file for `Vue*.vue` in webapp/vuejs/src/component/ . + +=== XML tags used +During the POC, few new properties or XML tags had been added but some of existing tags and properties had been distorted +for an exclusive Vue.Js use. + +* screen + ** in `>). + +Currently a specifics theme has created, to be able to remove most of css to avoid "conflict" with vuetify lib. This theme +is forced in the main decorator of the application. + +*Lookup* + +1. `LookupDecorator` is a specifics one for VueJs, but its usage is same as the standard one. It contain 2 main specificity + * for autocomplete return, all data are in viewData + * for search - result, when lookup receive parameters.lookupResult = "Y" only result form is sent to be injected in area lookup-results +2. `LookupForm - find` : + * it's necessary to add a hidden field +[source,XML] + + + * it's necessary to add 2 on-event-update-are, + + first one to send result in the area lookup-result + second one to collapse Find screenlet (if you want collapse it +[source,XML] + + +3. `LookupForm - list` : + * for the link return field, 2 on-field-event-update-area must be added + + first one to set Field which call the lookup with the correct value + + second one to close the modal +[source,XML] + + +4. `FindScreenDecorator`, is a specifics one for VueJs, but its usage is same as the standard one. Its specificity is about + ListResult update. Some modifications should be done in the find form + * add a hidden field onlyList +[source,XML] + + * add a on-event-update-area to update List result area +[source,XML] + + * add a on-event-update-area to collapse find screenlet (optional) +[source,XML] + + + +=== FrontJs Renderer +A *new viewHandler* and a new set of *renderer* had been created. + +A new package had been created in org.apache.ofbiz.widget.renderer named frontJs which contain all new renderer. + +In this package, there is a new FrontJsOutput class which allow to construct necessary elements whit the wished format. +An object of this class is instantiate at the begin of viewHandler process, then is completed by rendered object call. + +Uri *showPortletFj* of component examplefjs use the new viewHandler to return screen render result, showPortlet. + +This URI is used by Vue.Js application for display information gathering. + +New viewHandle return a json which contain 2 maps ( viewScreen and viewEntities ). + +viewScreen contain all information about display. +viewEntities contain all information about data set. + +In future revision, it will probably be feasible to only receive the data map. + +=== Specifics URI +there is a *showPortal* URI in vuejs (CommonScreen), which is the Start point of the VueJs application. + +*showPortlet* screen is redefine in this POC for simplification purpose, to only do what we want. + +*applicationMenu* is a new uri which return with the FrontJsScreenViewHandler the data (ViewScreen & ViewData) from application xml menu. + +In each fjs component, it's necessary to define a view-map entry applicationMenu pointing to a screen.xml with only the correct menu. + +=== REST URI +For OFBiz applications URI structure is : {resourceName}/{cover}/{Pkvalue: .*} + +So, for Example resource, it translate into : +|=== +| URI | method | Goal +| Example/find | get | find form to select options for list +| Example/list | get (via post)| Example list, + + => with a get (so without parameters) all the examples + + => with a post (parameters and _method="GET") examples selected by options +| Example/create | get | create form +| Example/edit/{exampleId} | get | edit form and data for the id sent +| Example/show/{exampleId} | get | show form and data for the id sent +| Example/summary/{exampleId} | get | summary form and data for the id sent +| Example/data/{exampleId} | get | just data for the id sent +| Example/change | post | create a example +| Example/change/{exampleId} | put (via post)| update example with exampleId +| Example/change/{exampleId} | delete | delete example with exampleId +|=== + +All these actions([cover]) is about screenlet (part of a page), if cover should be for a page, its name is suffix by Pg. + +[TIP] +HTTP request other than POST method cannot have parameters (other than in URI) so to be able to do a GET or a PUT with parameters +a workarround is to used a POST with added `` (or "PUT") in the form (or as one of the parameters). + +[CAUTION] +TOMCAT httpServlet.java not manage PATCH method, so it's not usable + +=== Added field +A new field had been added to PortalPagePortlet entity : *watcherName* which is the name of the watcher that fire portlet update. + +=== ftl +ftl file can be mixed with Vue.js app, so it's necessary to create a dedicated component by ftl migrated. Look at +<<_xml_tags_used, XML tags used>> on point `platform-specific`-`html` - `vuejs`. + +include::poc-situation_en.adoc[leveloffset=+1] + +include::poc-todo_en.adoc[leveloffset=+1] + +include::poc-useCase_en.adoc[leveloffset=+1] + +include::poc-vuejs-details_en.adoc[leveloffset=+1] + diff --git a/vuejs/src/docs/asciidoc/ofbiz-Resource-REST-reference.adoc b/vuejs/src/docs/asciidoc/ofbiz-Resource-REST-reference.adoc new file mode 100644 index 000000000..e1c5e5c3b --- /dev/null +++ b/vuejs/src/docs/asciidoc/ofbiz-Resource-REST-reference.adoc @@ -0,0 +1,97 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Apache OFBiz Resource REST Reference +The Apache OFBiz Project +Release trunk +:imagesdir: ./images +ifdef::backend-pdf[] +:title-logo-image: image::OFBiz-Logo.svg[Apache OFBiz Logo, pdfwidth=4.25in, align=center] +:source-highlighter: rouge +endif::[] + +== Introduction to OFBiz +Welcome to _Apache OFBiz_! A powerful top level Apache software project. +OFBiz is an Enterprise Resource Planning (ERP) System written in Java and +houses a large set of libraries, entities, services and features to run +all aspects of your business. + +This manual will describe all `resources` (on a REST point of view) available to manage business object. + +Currently ofbiz REST requests return for a `resource`: + + * data about it + * AND, most of time, user presentation data + +Resource is not entity (even if most of time it's the same), Resource is a functional view. + +Most of the OFBiz GUI is design by assembling some screenlet for one page. + +One of the configuration / personalisation task could be to choose its own assemblage. + +If you need to understand the process and bussiness object for the core application of this framework like the +Party Manager, Order Manager, Accounting system, and others you should read the "Apache OFBiz User Manual". + +If you wish to contribute to OFBiz and help make it better, you may wish to read +the "Apache OFBiz Developer Manual" for a deeper understanding of the +architectural concepts of the framework. + +== REST uri structure +All REST uri follow the model {webapp}/control/{ResourceName}/{cover}/{Pkvalue: .*} + +The first letter of ResourceName is uppercase. + +Classic cover are: + +|=== +| cover | method to use | Goal +| find | get | find form to select options for list +| list | get (via post)| Resource list, + + => with a get (so without parameters) all the resource rows + + => with a post (parameters and _method="GET") resources selected by options +| create | get | create form +| edit/{Pkvalue: .*} | get | edit form and data for the id sent +| show/{Pkvalue: .*} | get | show form and data for the id sent +| summary/{Pkvalue: .*} | get | summary form and data for the id sent +| data/{Pkvalue: .*} | get | just data for the id sent +| change | post | create a resource +| change/{Pkvalue: .*} | put (via post)| update example with the correspondig Id +| change/{Pkvalue: .*} | delete | delete example with the correspondig Id +|=== + + +In this documentation, for each ResourceName, the webapp mentioned is the one for which +the REST interface has been created for the first time. + +=== Core Business Applications +Most businesses share universal needs. They require accounting functionality, +managing customers, placing orders, book-keeping, invoicing and so on. + +OFBiz is designed so that such basic universal business needs are available +through a set of core business applications. These applications all share a +unified data-model with a set of unified services to implement this +functionality. + +Each core business application contain a group of resources, this documentation will help you +to find and understand which one do what + +All ResourceName are organize by Category, so there is one chapter by category. + +include::../../plugins/partymgrfjs/src/docs/asciidoc/screenlet-party.adoc[leveloffset=+1] + +include::../../plugins/examplefjs/src/docs/asciidoc/screenlet-example.adoc[leveloffset=+1] + diff --git a/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsFormRenderer.java b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsFormRenderer.java new file mode 100644 index 000000000..4f7ac101e --- /dev/null +++ b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsFormRenderer.java @@ -0,0 +1,2711 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +package org.apache.ofbiz.widget.renderer.frontjs; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.ofbiz.base.util.Debug; +import org.apache.ofbiz.base.util.UtilCodec; +import org.apache.ofbiz.base.util.UtilFormatOut; +import org.apache.ofbiz.base.util.UtilGenerics; +import org.apache.ofbiz.base.util.UtilHttp; +import org.apache.ofbiz.base.util.UtilMisc; +import org.apache.ofbiz.base.util.UtilProperties; +import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.base.util.string.FlexibleStringExpander; +import org.apache.ofbiz.entity.Delegator; +import org.apache.ofbiz.entity.GenericEntityException; +import org.apache.ofbiz.entity.model.ModelEntity; +import org.apache.ofbiz.entity.model.ModelReader; +import org.apache.ofbiz.webapp.control.RequestHandler; +import org.apache.ofbiz.webapp.taglib.ContentUrlTag; +import org.apache.ofbiz.widget.WidgetWorker; +import org.apache.ofbiz.widget.model.CommonWidgetModels; +import org.apache.ofbiz.widget.model.FieldInfo; +import org.apache.ofbiz.widget.model.ModelForm; +import org.apache.ofbiz.widget.model.ModelFormField; +//import org.apache.ofbiz.widget.model.ModelFormField.AutoComplete; +import org.apache.ofbiz.widget.model.ModelFormField.CheckField; +import org.apache.ofbiz.widget.model.ModelFormField.ContainerField; +import org.apache.ofbiz.widget.model.ModelFormField.DateFindField; +import org.apache.ofbiz.widget.model.ModelFormField.DateTimeField; +import org.apache.ofbiz.widget.model.ModelFormField.DisplayEntityField; +import org.apache.ofbiz.widget.model.ModelFormField.DisplayField; +import org.apache.ofbiz.widget.model.ModelFormField.DropDownField; +import org.apache.ofbiz.widget.model.ModelFormField.FieldInfoWithOptions; +import org.apache.ofbiz.widget.model.ModelFormField.FileField; +import org.apache.ofbiz.widget.model.ModelFormField.HiddenField; +import org.apache.ofbiz.widget.model.ModelFormField.HyperlinkField; +import org.apache.ofbiz.widget.model.ModelFormField.IgnoredField; +import org.apache.ofbiz.widget.model.ModelFormField.ImageField; +import org.apache.ofbiz.widget.model.ModelFormField.LookupField; +import org.apache.ofbiz.widget.model.ModelFormField.MenuField; +import org.apache.ofbiz.widget.model.ModelFormField.OptionValue; +import org.apache.ofbiz.widget.model.ModelFormField.PasswordField; +import org.apache.ofbiz.widget.model.ModelFormField.RadioField; +import org.apache.ofbiz.widget.model.ModelFormField.RangeFindField; +import org.apache.ofbiz.widget.model.ModelFormField.ResetField; +import org.apache.ofbiz.widget.model.ModelFormField.SubHyperlink; +import org.apache.ofbiz.widget.model.ModelFormField.SubmitField; +import org.apache.ofbiz.widget.model.ModelFormField.TextField; +import org.apache.ofbiz.widget.model.ModelFormField.TextFindField; +import org.apache.ofbiz.widget.model.ModelFormField.TextareaField; +//import org.apache.ofbiz.widget.model.ModelFormFieldBuilder; +import org.apache.ofbiz.widget.model.ModelScreenWidget; +import org.apache.ofbiz.widget.model.ThemeFactory; +import org.apache.ofbiz.widget.model.ModelForm.UpdateArea; +import org.apache.ofbiz.widget.renderer.FormRenderer; +import org.apache.ofbiz.widget.renderer.FormStringRenderer; +import org.apache.ofbiz.widget.renderer.Paginator; +import org.apache.ofbiz.widget.renderer.UtilHelpText; +import org.apache.ofbiz.widget.renderer.VisualTheme; + +//import com.ibm.icu.util.Calendar; + +//import org.apache.ofbiz.entity.GenericValue; + + +public final class FrontJsFormRenderer implements FormStringRenderer { + private static final String NOT_YET_SUPPORTED = "Not yet supported"; + private static final String MODULE = FrontJsFormRenderer.class.getName(); + private FrontJsOutput output; + private final UtilCodec.SimpleEncoder internalEncoder; + private final RequestHandler rh; + private final HttpServletRequest request; + private final HttpServletResponse response; + private final boolean javaScriptEnabled; + private final VisualTheme visualTheme; + private boolean renderPagination = true; + + public FrontJsFormRenderer(FrontJsOutput output, HttpServletRequest request, HttpServletResponse response) { + this.request = request; + this.response = response; + this.visualTheme = ThemeFactory.resolveVisualTheme(request); + ServletContext ctx = (ServletContext) request.getAttribute("servletContext"); + this.rh = (RequestHandler) ctx.getAttribute("_REQUEST_HANDLER_"); + this.javaScriptEnabled = UtilHttp.isJavaScriptEnabled(request); + internalEncoder = UtilCodec.getEncoder("string"); + this.output = output; + } + public void setRenderPagination(boolean renderPagination) { + this.renderPagination = renderPagination; + } + + private String encode(String value, ModelFormField modelFormField, Map context) { + if (UtilValidate.isEmpty(value)) { + return value; + } + UtilCodec.SimpleEncoder encoder = (UtilCodec.SimpleEncoder) context.get("simpleEncoder"); + if (modelFormField.getEncodeOutput() && encoder != null) { + value = encoder.encode(value); + } else { + value = internalEncoder.encode(value); + } + return value; + } + + private static String encodeDoubleQuotes(String htmlString) { + return htmlString.replaceAll("\"", "\\\\\""); + } + + private List getPkList(String entityName, ModelReader entityModelReader) { + ModelEntity modelEntity = null; + if (UtilValidate.isNotEmpty(entityName)) { + try { + modelEntity = entityModelReader.getModelEntity(entityName); + } catch (GenericEntityException e) { + Debug.logError(e, MODULE); + } + if (modelEntity == null) { + throw new IllegalArgumentException("Error finding Entity with name " + entityName + + " for defaut-entity-name in a form widget"); + } else { + return modelEntity.getPkFieldNames(); + } + } + return null; + } + + public void renderLabel(Map context, ModelScreenWidget.Label label) { + String labelText = label.getText(context); + if (UtilValidate.isEmpty(labelText)) { + // nothing to render + return; + } + Map attributes = new HashMap<>(); + attributes.put("text", labelText); + this.output.putScreen("Label", attributes); + } + + public void renderDisplayField(Appendable writer, Map context, DisplayField displayField) { + ModelFormField modelFormField = displayField.getModelFormField(); + String idName = modelFormField.getCurrentContainerId(context); + String description = displayField.getDescription(context); + String type = displayField.getType(); + String imageLocation = displayField.getImageLocation(context); + String title = ""; + String name = modelFormField.getName(); + Map attributes = new HashMap<>(); + Integer size = Integer.valueOf("0"); + if (UtilValidate.isNotEmpty(displayField.getSize())) { + try { + size = Integer.parseInt(displayField.getSize()); + } catch (NumberFormatException nfe) { + Debug.logError(nfe, "Error reading size of a field fieldName=" + displayField.getModelFormField().getFieldName() + + " FormName= " + displayField.getModelFormField().getModelForm().getName(), MODULE); + } + Debug.logWarning("displayField attribute size is used in form with name=" + modelFormField.getModelForm().getName() + + " it's not manage by FrontFjRenderer", MODULE); + attributes.put("size", displayField.getSize()); + } +// not yet use case for this, but substring should be done on vuejs +// if (UtilValidate.isNotEmpty(description) && size > 0 && description.length() > size) { +// title = description; +// description = description.substring(0, size - 8) + "..." + description.substring(description.length() - 5); +// } + + if ("single".equals(modelFormField.getModelForm().getType())) { this.addTitle(attributes, modelFormField, context);} + attributes.put("formName", displayField.getModelFormField().getModelForm().getName()); + attributes.put("description", encodeDoubleQuotes(description)); + attributes.put("name", name); + + ModelFormField.InPlaceEditor inPlaceEditor = displayField.getInPlaceEditor(); + if (inPlaceEditor != null) { + attributes.put("inPlaceEditor", true); + String url = inPlaceEditor.getUrl(context); + attributes.put("url", url); + Map fieldMap = inPlaceEditor.getFieldMap(context); + attributes.put("fieldMap", fieldMap); + if (UtilValidate.isNotEmpty(inPlaceEditor.getSavingText())) { + Debug.logWarning("displayField in-place-editor saving-text attribute is used in form with name=" + + modelFormField.getModelForm().getName() + + " it's not manage by FrontFjRenderer", MODULE); + attributes.put("savingText", inPlaceEditor.getSavingText()); + } + } + + if (UtilValidate.isNotEmpty(type)) {attributes.put("type", type);} + if (UtilValidate.isNotEmpty(size)) {attributes.put("size", size);} + if (UtilValidate.isNotEmpty(imageLocation)) {attributes.put("imageLocation", imageLocation);} + if (UtilValidate.isNotEmpty(idName)) { attributes.put("idName", idName);} + if (UtilValidate.isNotEmpty(title)) { attributes.put("title", title);} + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { attributes.put("class", modelFormField.getWidgetStyle());} + if (UtilValidate.isNotEmpty(modelFormField.shouldBeRed(context))) { attributes.put("alert", "true");} + this.appendTooltip(attributes, context, modelFormField); + + // putRecord only if attribute "description" not exist for the context, HyperlinkField hyperlinkField) { + ModelFormField modelFormField = hyperlinkField.getModelFormField(); + HashMap attributes = new HashMap<>(); + if (!hyperlinkField.getTarget(context).isEmpty()) { + attributes.put("target", hyperlinkField.getTarget(context)); + } + if (!hyperlinkField.getTargetWindow(context).isEmpty()) { + attributes.put("targetWindow", hyperlinkField.getTargetWindow(context)); + } + Map parameterMap = hyperlinkField.getParameterMap(context, modelFormField.getEntityName(), modelFormField.getServiceName()); + if (!parameterMap.isEmpty()) { + attributes.put("parameterMap", parameterMap); + } + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) attributes.put("style", modelFormField.getWidgetStyle()); + if (UtilValidate.isNotEmpty(hyperlinkField.getRequestConfirmation())) + attributes.put("requestConfirmation", hyperlinkField.getRequestConfirmation()); + if (UtilValidate.isNotEmpty(hyperlinkField.getConfirmationMsg(context))) + attributes.put("confirmationMessage", hyperlinkField.getConfirmationMsg(context)); + if (UtilValidate.isNotEmpty(hyperlinkField.getImageLocation(context))) + attributes.put("imgSrc", hyperlinkField.getImageLocation(context)); + if (UtilValidate.isNotEmpty(hyperlinkField.getImageTitle(context))) + attributes.put("imgTitle", hyperlinkField.getImageTitle(context)); + if (UtilValidate.isNotEmpty(hyperlinkField.getUrlMode())) + attributes.put("urlMode", hyperlinkField.getUrlMode()); + if (UtilValidate.isNotEmpty(hyperlinkField.getLinkType())) + attributes.put("linkType", hyperlinkField.getLinkType()); + List clickUpdateAreas = modelFormField.getOnClickUpdateAreas(context); + if (!clickUpdateAreas.isEmpty()) { + List> updateAreasValid = new LinkedList<>(); + for (UpdateArea updateArea : clickUpdateAreas) { + updateAreasValid.add(updateArea.toMap(context)); + } + attributes.put("clickUpdateAreas", updateAreasValid); + } + attributes.put("description", hyperlinkField.getDescription(context)); + String value = modelFormField.getEntry(context); + if (hyperlinkField.getDescription(context).equals(value)) { + String key = modelFormField.getName(); + this.output.putScreen("HyperlinkField", attributes, key, value); + } else { + this.output.putScreen("HyperlinkField", attributes); + } + } + + public void renderMenuField(Appendable writer, Map context, MenuField menuField) throws IOException { + throw new IOException("FrontJsRender: include-menu field, not yet implemented in form for form name=" + + menuField.getModelFormField().getModelForm().getName()); + //menuField.renderFieldString(writer, context, null); + //this.output.putScreen("MenuField", new HashMap()); + } + + /** + * With frontJs, it's better to have title field attributes as field attributes not in an other element at the same level. + * @param attributes + * @param modelFormField + * @param context + */ + private void addTitle(Map attributes, ModelFormField modelFormField, Map context) { + attributes.put("fieldTitle", modelFormField.getTitle(context)); + if (UtilValidate.isNotEmpty(modelFormField.getTitleStyle())) attributes.put("titlestyle", modelFormField.getTitleStyle()); + if ( modelFormField.getRequiredField()) { + String requiredStyle = modelFormField.getRequiredFieldStyle(); + if (UtilValidate.isNotEmpty(requiredStyle)) { + attributes.put("titlestyle", requiredStyle); + } + } + String displayHelpText = UtilProperties.getPropertyValue("widget", "widget.form.displayhelpText"); + if ("Y".equals(displayHelpText) && UtilValidate.isNotEmpty(modelFormField.getEntityName()) ) { + Delegator delegator = WidgetWorker.getDelegator(context); + Locale locale = (Locale) context.get("locale"); + String entityName = modelFormField.getEntityName(); + String fieldName = modelFormField.getFieldName(); + String helpText = UtilHelpText.getEntityFieldDescription(entityName, fieldName, delegator, locale); + attributes.put("fieldHelpText", encodeDoubleQuotes(helpText)); + } + } + + private void addAlertAndClass(Map attributes, ModelFormField modelFormField, Map context) { + String name = modelFormField.getParameterName(context); + String formName = modelFormField.getModelForm().getName(); + // First attribute alert and className generate only a warning + String className = ""; + String alert = "false"; + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { + className = modelFormField.getWidgetStyle(); + if (modelFormField.shouldBeRed(context)) { + alert = "true"; + } + } + //check for required field style on single forms + if ("single".equals(modelFormField.getModelForm().getType()) && modelFormField.getRequiredField()) { + String requiredStyle = modelFormField.getRequiredFieldStyle(); + if (UtilValidate.isEmpty(requiredStyle)) { + requiredStyle = "required"; + } + if (UtilValidate.isEmpty(className)) { + className = requiredStyle; + } else { + className = requiredStyle + " " + className; + } + } + if (UtilValidate.isNotEmpty(className) || "true".equals(alert)) { + Debug.logWarning("Field with alert or class attribute is used for field name="+name+ + " in form with name="+formName + + " it's not manage by FrontFjRenderer", MODULE); + if (UtilValidate.isNotEmpty(className)) attributes.put("className", className); + if ("true".equals(alert)) attributes.put("alert", alert); + } + } + + public void renderTextField(Appendable writer, Map context, TextField textField) throws IOException { + ModelFormField modelFormField = textField.getModelFormField(); + String name = modelFormField.getParameterName(context); + String value = modelFormField.getEntry(context, textField.getDefaultValue(context)); + Integer textSize = textField.getSize(); + Integer maxlength = -1; + if (textField.getMaxlength() != null) { + maxlength = textField.getMaxlength(); + } + String id = modelFormField.getCurrentContainerId(context); + String formName = textField.getModelFormField().getModelForm().getName(); + + Map attributes = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(attributes, modelFormField, context); + attributes.put("formName", formName); + attributes.put("name", name); + attributes.put("value", value); + attributes.put("textSize", textSize); // not manage by frontJs + if (maxlength > -1) attributes.put("maxlength", maxlength); + if (UtilValidate.isNotEmpty(id)) attributes.put("id", id); + if (UtilValidate.isNotEmpty(textField.getMask())) attributes.put("mask", textField.getMask()); + this.appendTooltip(attributes, context, modelFormField); + this.addAsterisks(attributes, context, modelFormField); + this.output.putScreen("TextField", attributes, name, value); + + // All not manage attributes + // First attribute alert and className generate only a warning + this.addAlertAndClass(attributes, modelFormField, context); + // Second attributes list, generate an error + String placeholder = textField.getPlaceholder(context); + List updateAreas = modelFormField.getOnChangeUpdateAreas(); + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + boolean disabled = modelFormField.getDisabled(); + String ajaxUrl = createAjaxParamsFromUpdateAreas(updateAreas, "", context); + boolean readonly = textField.getReadonly(); + String tabindex = modelFormField.getTabindex(); + if (disabled || readonly || !textField.getClientAutocompleteField() + || UtilValidate.isNotEmpty(event) || UtilValidate.isNotEmpty(action) + || UtilValidate.isNotEmpty(placeholder) || UtilValidate.isNotEmpty(tabindex)) { + + if (UtilValidate.isNotEmpty(event)) attributes.put("event", event); + if (UtilValidate.isNotEmpty(action)) attributes.put("action", action); + if (! textField.getClientAutocompleteField()) attributes.put("clientAutocomplete", "false"); //the default value is true + if (UtilValidate.isNotEmpty(ajaxUrl)) attributes.put("ajaxUrl", ajaxUrl); + if (UtilValidate.isNotEmpty(placeholder)) attributes.put("placeholder", placeholder); + if (UtilValidate.isNotEmpty(tabindex)) attributes.put("tabindex", tabindex); + if (disabled) attributes.put("disabled", disabled); + if (readonly) attributes.put("readonly", readonly); + throw new IOException("FrontJsRender: a attribute is not yet implemented for text-field name=" + name + + " in form for form name=" + modelFormField.getModelForm().getName() + + " attribute is one of : disabled("+disabled+"), readonly("+readonly + + "), !clientAutocomplete("+!textField.getClientAutocompleteField()+"), event("+event + + "), action("+action+"), updateAreas("+updateAreas+"), ajaxUrl("+ajaxUrl+"), placeholder("+placeholder + + "), tabindex("+tabindex+")"); + + } + } + + public void renderTextareaField(Appendable writer, Map context, TextareaField textareaField) throws IOException { + ModelFormField modelFormField = textareaField.getModelFormField(); + String name = modelFormField.getParameterName(context); + String id = modelFormField.getCurrentContainerId(context); + Integer maxlength = -1; + if (textareaField.getMaxlength() != null) { + maxlength = textareaField.getMaxlength(); + } + String value = modelFormField.getEntry(context, textareaField.getDefaultValue(context)); + String formName = modelFormField.getModelForm().getName(); + Map attributes = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(attributes, modelFormField, context); + + attributes.put("formName", formName); + attributes.put("name", name); + attributes.put("value", value); + if (maxlength > -1) attributes.put("maxlength", maxlength); + if (UtilValidate.isNotEmpty(id)) attributes.put("id", id); + this.appendTooltip(attributes, context, modelFormField); + this.addAsterisks(attributes, context, modelFormField); + this.output.putScreen("TextAreaField", attributes, name, value); + + // All not manage attributes + // First attribute alert and className generate only a warning + this.addAlertAndClass(attributes, modelFormField, context); + // cols and rows are always present because they have default value + attributes.put("cols", textareaField.getCols()); // default value 60 + attributes.put("rows", textareaField.getRows()); // default value 2 + if (textareaField.getCols() != 60 || textareaField.getRows() !=2 ) { + Debug.logWarning("textAreaField with cols or/and rows attribute is used for field name="+name+ + " in form with name="+formName + + " it's not manage by FrontFjRenderer", MODULE); + } + + if (textareaField.getVisualEditorEnable()) { + attributes.put("visualEditorEnable", "true"); + if (UtilValidate.isNotEmpty(textareaField.getVisualEditorButtons(context))) { + attributes.put("disabled", textareaField.getVisualEditorButtons(context)); + } else { + attributes.put("disabled", "maxi"); + } + } + String tabindex = modelFormField.getTabindex(); + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + boolean disabled = modelFormField.getDisabled(); + if (textareaField.isReadOnly() || textareaField.getVisualEditorEnable() + || UtilValidate.isNotEmpty(event) || UtilValidate.isNotEmpty(action) + || disabled || UtilValidate.isNotEmpty(tabindex)) { + + Map userLogin = UtilGenerics.cast(context.get("userLogin")); + String language = "en"; + if (userLogin != null) { + language = UtilValidate.isEmpty((String) userLogin.get("lastLocale")) ? "en" : (String) userLogin.get("lastLocale"); + } + attributes.put("language", language); + if (textareaField.isReadOnly()) attributes.put("readonly", "readonly"); + if (UtilValidate.isNotEmpty(event)) attributes.put("event", event); + if (UtilValidate.isNotEmpty(action)) attributes.put("action", action); + if (disabled) attributes.put("disabled", disabled); + if (UtilValidate.isNotEmpty(tabindex)) attributes.put("tabindex", tabindex); + throw new IOException("FrontJsRender: a attribute is not yet implemented for textArea-field name=" + name + + " in form for form name=" + modelFormField.getModelForm().getName() + + " attribute is one of : readonly("+textareaField.isReadOnly() + + "), visualEditorEnable("+textareaField.getVisualEditorEnable() + + "), editorButtons("+textareaField.getVisualEditorButtons(context) + + "), event("+event + "), action("+action + + "), disabled("+disabled+ "), tabindex("+tabindex+")"); + } + } + + public void renderDateTimeField(Appendable writer, Map context, DateTimeField dateTimeField) throws IOException { + ModelFormField modelFormField = dateTimeField.getModelFormField(); + String name = modelFormField.getParameterName(context);; + String formName = modelFormField.getModelForm().getName(); + String id = modelFormField.getCurrentContainerId(context); + Map attributes = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(attributes, modelFormField, context); + + String contextValue = modelFormField.getEntry(context, dateTimeField.getDefaultValue(context)); + + attributes.put("formName", formName); + attributes.put("name", name); + attributes.put("value", contextValue); + if ("12".equals(dateTimeField.getClock())) attributes.put("isTwelveHour", "Y"); + if (UtilValidate.isNotEmpty(id)) attributes.put("id", id); // not yet used in vuejs, waiting use case + this.addAsterisks(attributes, context, modelFormField); + this.appendTooltip(attributes, context, modelFormField); + this.output.putScreen("DateTimeField", attributes, name, contextValue); + + // All not-manage attributes + // First attribute alert and className generate only a warning + this.addAlertAndClass(attributes, modelFormField, context); + // second: mask=, nothing to do currently because with material design it's managed + /* + String mask = dateTimeField.getMask(); + String formattedMask = ""; + if ("Y".equals(mask)) { + if ("date".equals(dateTimeField.getType())) { + formattedMask = "9999-99-99"; + } else if ("time".equals(dateTimeField.getType())) { + formattedMask = "99:99:99"; + } else if ("timestamp".equals(dateTimeField.getType())) { + formattedMask = "9999-99-99 99:99:99"; + } + attributes.put("formattedMask", formattedMask); + } + */ + // third, warning, with material design it's always text and "dropdown", so put a warning + // to check if current management is correct for field where it's used + if ("time-dropdown".equals(dateTimeField.getInputMethod()) || !"1".equals(dateTimeField.getStep()) ) { + Debug.logWarning("date-time Field with time-dropdown or/and step attribute is used for field name="+name+ + " in form with name="+formName + + " it's not manage by FrontFjRenderer", MODULE); + } + //last, list of attributes which generate error if present because not manage and should be. + FlexibleStringExpander defaultValue = dateTimeField.getDefaultValue(); + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + boolean disabled = modelFormField.getDisabled(); + String tabindex = modelFormField.getTabindex(); + if (! "timestamp".equals(dateTimeField.getType()) + || UtilValidate.isNotEmpty(event) || UtilValidate.isNotEmpty(action) || disabled + || UtilValidate.isNotEmpty(defaultValue) || UtilValidate.isNotEmpty(tabindex)) { + + attributes.put("dateType", dateTimeField.getType()); + if (UtilValidate.isNotEmpty(event)) attributes.put("event", event); + if (UtilValidate.isNotEmpty(action)) attributes.put("action", action); + if (disabled) attributes.put("disabled", disabled); + if (UtilValidate.isNotEmpty(tabindex)) attributes.put("tabindex", tabindex); + if (UtilValidate.isNotEmpty(defaultValue)) attributes.put("defaultDateTimeString", + dateTimeField.getDefaultDateTimeString(context)); + throw new IOException("FrontJsRender: a attribute is not yet implemented for date-time-field name=" + name + + " in form for form name=" + modelFormField.getModelForm().getName() + + " attribute is one of : dateType("+dateTimeField.getType() + + "), defaultValue("+dateTimeField.getDefaultDateTimeString(context) + + "), event("+event + "), action("+action + "), tabindex("+tabindex+")"); + } + } + + public void renderDropDownField(Appendable writer, Map context, DropDownField dropDownField) throws IOException { + ModelFormField modelFormField = dropDownField.getModelFormField(); + ModelForm modelForm = modelFormField.getModelForm(); + String id = modelFormField.getCurrentContainerId(context); + String name = modelFormField.getName(); + String formName = modelForm.getName(); + String currentValue = modelFormField.getEntry(context); + List allOptionValues = dropDownField.getAllOptionValues(context, WidgetWorker.getDelegator(context)); + Integer textSize = 0; + if (UtilValidate.isNotEmpty(dropDownField.getTextSize())) { + try { + textSize = Integer.parseInt(dropDownField.getTextSize()); + } catch (NumberFormatException nfe) { + Debug.logError(nfe, "Error reading size of a field fieldName=" + dropDownField.getModelFormField().getFieldName() + " FormName= " + dropDownField.getModelFormField().getModelForm().getName(), MODULE); + } + if (textSize > 0 && UtilValidate.isNotEmpty(currentValue) && currentValue.length() > textSize) { + currentValue = currentValue.substring(0, textSize - 8) + "..." + currentValue.substring(currentValue.length() - 5); + } + } + String explicitDescription; + String currentDescription = null; + if (UtilValidate.isNotEmpty(currentValue)) { + for (OptionValue optionValue : allOptionValues) { + if (optionValue.getKey().equals(currentValue)) { + currentDescription = optionValue.getDescription(); + break; + } + } + } + explicitDescription = (currentDescription != null ? currentDescription : dropDownField.getCurrentDescription(context)); + if (UtilValidate.isEmpty(explicitDescription)) { + explicitDescription = (FieldInfoWithOptions.getDescriptionForOptionKey(currentValue, allOptionValues)); + } + if (textSize > 0 && UtilValidate.isNotEmpty(explicitDescription) && explicitDescription.length() > textSize) { + explicitDescription = explicitDescription.substring(0, textSize - 8) + "..." + explicitDescription.substring(explicitDescription.length() - 5); + } + explicitDescription = encode(explicitDescription, modelFormField, context); + /* allow-multiple is not yet manage, currently generate an error message + List currentValueList = null; + if (UtilValidate.isNotEmpty(currentValue) && dropDownField.getAllowMultiple()) { + // If currentValue is Array, it will start with [ + if (currentValue.startsWith("[")) { + currentValueList = StringUtil.toList(currentValue); + } else { + currentValueList = UtilMisc.toList(currentValue); + } + }*/ + + List> options = new ArrayList<>(); + Iterator optionValueIter = allOptionValues.iterator(); + while (optionValueIter.hasNext()) { + OptionValue optionValue = optionValueIter.next(); + + Map option = new HashMap<>(); + String key = encode(optionValue.getKey(), modelFormField, context); + option.put("key", key); + String description = optionValue.getDescription(); + if (textSize > 0 && description.length() > textSize) { + description = description.substring(0, textSize - 8) + "..." + description.substring(description.length() - 5); + } + option.put("description", encode(description.replaceAll("'", "\\\\\'"), modelFormField, context)); // replaceAll("'", "\\\\\'") related to OFBIZ-6504 + /* allow-multiple is not yet manage, currently generate an error message + if (UtilValidate.isNotEmpty(currentValueList)) { + option.put("selected", "selected"); + } + */ + options.add(option); + } + + Map cb = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(cb, modelFormField, context); + this.addAsterisks(cb, context, modelFormField); + cb.put("name", name); + cb.put("formName", formName); + cb.put("id", id); + cb.put("currentValue", currentValue); + cb.put("options", options); + String noCurrentSelectedKey = dropDownField.getNoCurrentSelectedKey(context); + if (UtilValidate.isNotEmpty(noCurrentSelectedKey)) cb.put("noCurrentSelectedKey", noCurrentSelectedKey); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("DropDownField", cb, name, explicitDescription); + + // not used by Vue.Js, but seems to be usable ! + cb.put("explicitDescription", explicitDescription); + + // All not-manage attributes + // First attribute alert and className generate only a warning + this.addAlertAndClass(cb, modelFormField, context); + //if (dropDownField.getAllowEmpty()) cb.put("allowEmpty", "Y"); TODO test what is manage + //Second, list of attributes which generate error if present because not manage and should be. + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + boolean disabled = modelFormField.getDisabled(); + String tabindex = modelFormField.getTabindex(); + String conditionGroup = modelFormField.getConditionGroup(); + if ( dropDownField.getAllowMultiple() + || ! "selected".equals(dropDownField.getCurrent()) + || dropDownField.getOtherFieldSize() > 0 + || ! "1".equals(dropDownField.getSize()) + || dropDownField.getAutoComplete() != null + || UtilValidate.isNotEmpty(event) || UtilValidate.isNotEmpty(action) || disabled + || UtilValidate.isNotEmpty(tabindex) || UtilValidate.isNotEmpty(conditionGroup)) { + + if (dropDownField.getAllowMultiple()) cb.put("multiple", "multiple"); + if (! "selected".equals(dropDownField.getCurrent())) cb.put("dDFCurrent", dropDownField.getCurrent()); + if (UtilValidate.isNotEmpty(currentValue) && "first-in-list".equals(dropDownField.getCurrent())) { + cb.put("firstInList", "first-in-list"); // if the current value should go first, stick it in + } + if (! "1".equals(dropDownField.getSize())) cb.put("size", dropDownField.getSize()); + // for autocomplete see the commented code just after + // for otherFieldSize see the commented code just after + if (UtilValidate.isNotEmpty(event)) cb.put("event", event); + if (UtilValidate.isNotEmpty(action)) cb.put("action", action); + if (UtilValidate.isNotEmpty(tabindex)) cb.put("tabindex", tabindex); + if (UtilValidate.isNotEmpty(conditionGroup)) cb.put("conditionGroup", conditionGroup); + throw new IOException("FrontJsRender: a attribute is not yet implemented for drop-down-field name=" + name + + " in form for form name=" + modelFormField.getModelForm().getName() + + " attribute is one of : noCurrentSelectedKey("+noCurrentSelectedKey + + "), allow-multiple("+dropDownField.getAllowMultiple() + + "), current("+dropDownField.getCurrent() + + "), size("+dropDownField.getSize() + + "), other-field-size("+dropDownField.getOtherFieldSize() + + "), auto-complete("+dropDownField.getAutoComplete() + + "), event("+event + "), action("+action+"), disabled("+disabled + + "), tabindex("+tabindex+"), conditionGroup("+conditionGroup+")"); + } + + /* Begin autocomplete options management + AutoComplete autoComplete = dropDownField.getAutoComplete(); + boolean ajaxEnabled = autoComplete != null && this.javaScriptEnabled; + cb.put("ajaxEnabled", ajaxEnabled); + StringBuilder ajaxOptions = new StringBuilder(); + int count = 0; + Iterator optionValueIter2 = allOptionValues.iterator(); // new iterator because, + //I not know how to re-initialize it the beginning of the list + while (optionValueIter2.hasNext()) { + OptionValue optionValue = optionValueIter2.next(); + if (ajaxEnabled) { + count++; + ajaxOptions.append(optionValue.getKey()).append(": "); + ajaxOptions.append(" '").append(optionValue.getDescription()).append("'"); + if (count != allOptionValues.size()) { + ajaxOptions.append(", "); + } + } + } + cb.put("ajaxOptions", ajaxOptions.toString()); + cb.put("frequency", autoComplete.getFrequency()); + cb.put("minChars", autoComplete.getMinChars()); + cb.put("choices", autoComplete.getChoices()); + cb.put("autoSelect", autoComplete.getAutoSelect()); + cb.put("partialSearch", autoComplete.getPartialSearch()); + cb.put("partialChars", autoComplete.getPartialChars()); + cb.put("ignoreCase", autoComplete.getIgnoreCase()); + cb.put("fullSearch", autoComplete.getFullSearch()); + // End autocomplete options management + + // Begin otherFieldSize management (if > 0) + String otherValue = ""; + String otherFieldName = ""; + int otherFieldSize = dropDownField.getOtherFieldSize(); + if (otherFieldSize > 0) { + otherFieldName = dropDownField.getParameterNameOther(context); + } + // Adapted from work by Yucca Korpela + // http://www.cs.tut.fi/~jkorpela/forms/combo.html + if (otherFieldSize > 0) { + fieldName = modelFormField.getParameterName(context); + Map dataMap = modelFormField.getMap(context); + if (dataMap == null) { + dataMap = context; + } + Object otherValueObj = dataMap.get(otherFieldName); + otherValue = (otherValueObj == null) ? "" : otherValueObj.toString(); + cb.put("otherFieldName", otherFieldName); + cb.put("otherValue", otherValue); + cb.put("otherFieldSize", otherFieldSize); + + } + */ + + } + + public void renderCheckField(Appendable writer, Map context, CheckField checkField) throws IOException { + ModelFormField modelFormField = checkField.getModelFormField(); + String currentValue = modelFormField.getEntry(context); + Boolean allChecked = checkField.isAllChecked(context); + String id = modelFormField.getCurrentContainerId(context); + String name = modelFormField.getName(); + List allOptionValues = checkField.getAllOptionValues(context, WidgetWorker.getDelegator(context)); + List> optionValues = new LinkedList<>(); + for (OptionValue optionValue : allOptionValues) { + optionValues.add(UtilMisc.toMap(optionValue.getKey(), optionValue.getDescription())); + } + Map cb = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(cb, modelFormField, context); + this.addAsterisks(cb, context, modelFormField); + cb.put("optionValues", optionValues); + cb.put("id", id); + if (allChecked) cb.put("allChecked", true); + cb.put("currentValue", currentValue); + cb.put("name", name); + + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("CheckField", cb, name, currentValue); + if (UtilValidate.isNotEmpty(cb)) { // condition always true, so IOException always occurs + throw new IOException("FrontJsRender: check-field is not yet manage for field name=" + name + + " in form for form name=" + modelFormField.getModelForm().getName()); + } + // All not-manage attributes + // First attribute alert and className generate only a warning + this.addAlertAndClass(cb, modelFormField, context); + //Second, list of attributes which generate error if present because not manage and should be. + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + boolean disabled = modelFormField.getDisabled(); + String tabindex = modelFormField.getTabindex(); + String conditionGroup = modelFormField.getConditionGroup(); + if (disabled + || UtilValidate.isNotEmpty(event) || UtilValidate.isNotEmpty(action) || disabled + || UtilValidate.isNotEmpty(conditionGroup) || UtilValidate.isNotEmpty(tabindex)) { + if (UtilValidate.isNotEmpty(event)) cb.put("event", event); + if (UtilValidate.isNotEmpty(action)) cb.put("action", action); + if (disabled) cb.put("disabled", disabled); + if (UtilValidate.isNotEmpty(tabindex)) cb.put("tabindex", tabindex); + if (UtilValidate.isNotEmpty(conditionGroup)) cb.put("conditionGroup", conditionGroup); + throw new IOException("FrontJsRender: a attribute is not yet implemented for check-field name=" + name + + " in form for form name=" + modelFormField.getModelForm().getName() + + " attribute is one of : disabled("+disabled + + "), event("+event + "), action("+action+"), disabled("+disabled + + "), tabindex("+tabindex+"), conditionGroup("+conditionGroup+")"); + + } + } + + public void renderRadioField(Appendable writer, Map context, RadioField radioField) throws IOException { + ModelFormField modelFormField = radioField.getModelFormField(); + String currentValue = modelFormField.getEntry(context); + String name = modelFormField.getName(); + String formName = radioField.getModelFormField().getModelForm().getName(); + + Map cb = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(cb, modelFormField, context); + + List clickUpdateAreas = modelFormField.getOnClickUpdateAreas(); + if (!clickUpdateAreas.isEmpty()) { + List> clickUpdateAreasList = new ArrayList<>(); + for (UpdateArea updateArea : clickUpdateAreas) { + clickUpdateAreasList.add(updateArea.toMap(context)); + } + cb.put("clickUpdateAreas", clickUpdateAreasList); + } + + List allOptionValues = radioField.getAllOptionValues(context, WidgetWorker.getDelegator(context)); + List> items = new ArrayList<>(); + for (OptionValue optionValue : allOptionValues) { + items.add(UtilMisc.toMap( + "key", optionValue.getKey(), + "description",encode(optionValue.getDescription(), modelFormField, context))); + } + cb.put("items", items); + + cb.put("currentValue", currentValue); + cb.put("name", name); + cb.put("formName", formName); + this.addAsterisks(cb, context, modelFormField); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("RadioField", cb, name, currentValue); + + // All not-manage attributes + // First attribute alert and className generate only a warning + this.addAlertAndClass(cb, modelFormField, context); + //Second, list of attributes which generate error if present because not manage and should be. + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + boolean disabled = modelFormField.getDisabled(); + String tabindex = modelFormField.getTabindex(); + String conditionGroup = modelFormField.getConditionGroup(); + String noCurrentSelectedKey = radioField.getNoCurrentSelectedKey(context); // info: it is manage for drop-donw + if ( UtilValidate.isNotEmpty(noCurrentSelectedKey) + || UtilValidate.isNotEmpty(event) || UtilValidate.isNotEmpty(action) || disabled + || UtilValidate.isNotEmpty(conditionGroup) || UtilValidate.isNotEmpty(tabindex)) { + if (UtilValidate.isNotEmpty(event)) cb.put("event", event); + if (UtilValidate.isNotEmpty(action)) cb.put("action", action); + if (disabled) cb.put("disabled", disabled); + if (UtilValidate.isNotEmpty(tabindex)) cb.put("tabindex", tabindex); + if (UtilValidate.isNotEmpty(conditionGroup)) cb.put("conditionGroup", conditionGroup); + throw new IOException("FrontJsRender: a attribute is not yet implemented for radio-field name=" + name + + " in form for form name=" + modelFormField.getModelForm().getName() + + " attribute is one of: noCurrentSelectedKey("+noCurrentSelectedKey + + "), event("+event + "), action("+action+"), disabled("+disabled + + "), tabindex("+tabindex+"), conditionGroup("+conditionGroup+")"); + + } + } + + public void renderSubmitField(Appendable writer, Map context, SubmitField submitField) throws IOException { + ModelFormField modelFormField = submitField.getModelFormField(); + ModelForm modelForm = modelFormField.getModelForm(); + String title = modelFormField.getTitle(context); + String name = modelFormField.getName(); + String formName = FormRenderer.getCurrentFormName(modelForm, context); + String formId = FormRenderer.getCurrentContainerId(modelForm, context); + + Map cb = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(cb, modelFormField, context); + + cb.put("formName", formName); + cb.put("title", encode(title, modelFormField, context)); + cb.put("name", name); + + List updateAreas = modelForm.getOnSubmitUpdateAreas(context); + // This is here for backwards compatibility. Use on-event-update-area + // elements instead. + String backgroundSubmitRefreshTarget = submitField.getBackgroundSubmitRefreshTarget(context); + if (UtilValidate.isNotEmpty(backgroundSubmitRefreshTarget)) { + if (updateAreas == null) { + updateAreas = new LinkedList<>(); + } + updateAreas.add(new UpdateArea("submit", formId, backgroundSubmitRefreshTarget)); + } + if (!updateAreas.isEmpty()) { + List> updateAreasValid = new LinkedList<>(); + for (UpdateArea updateArea : updateAreas) { + updateAreasValid.add(updateArea.toMap(context)); + } + cb.put("updateAreas", updateAreasValid); + } + + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("SubmitField", cb); + + // All not-manage attributes + // First attribute alert and className generate only a warning + this.addAlertAndClass(cb, modelFormField, context); + if (UtilValidate.isNotEmpty(submitField.getButtonType()) && ! "button".equals(submitField.getButtonType())) { + Debug.logWarning("Submit Field with button-type = "+submitField.getButtonType()+" is used for field name="+name+ + " in form with name="+formName + + " it's not manage by FrontFjRenderer", MODULE); + cb.put("buttonType", submitField.getButtonType()); + } + //Second, list of attributes which generate error if present because not manage and should be. + String imgSrc = submitField.getImageLocation(context); + String confirmation = submitField.getConfirmation(context); + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + boolean disabled = modelFormField.getDisabled(); + String tabindex = modelFormField.getTabindex(); + String conditionGroup = modelFormField.getConditionGroup(); + if ( UtilValidate.isNotEmpty(imgSrc) || UtilValidate.isNotEmpty(confirmation) + || UtilValidate.isNotEmpty(event) || UtilValidate.isNotEmpty(action) || disabled + || UtilValidate.isNotEmpty(conditionGroup) || UtilValidate.isNotEmpty(tabindex)) { + if (UtilValidate.isNotEmpty(imgSrc)) cb.put("imgSrc", imgSrc); + if (UtilValidate.isNotEmpty(confirmation)) cb.put("confirmation", confirmation); + if (UtilValidate.isNotEmpty(event)) cb.put("event", event); + if (UtilValidate.isNotEmpty(action)) cb.put("action", action); + if (disabled) cb.put("disabled", disabled); + if (UtilValidate.isNotEmpty(tabindex)) cb.put("tabindex", tabindex); + if (UtilValidate.isNotEmpty(conditionGroup)) cb.put("conditionGroup", conditionGroup); + throw new IOException("FrontJsRender: a attribute is not yet implemented for submit-field name=" + name + + " in form for form name=" + modelFormField.getModelForm().getName() + + " attribute is one of: imgSrc("+ imgSrc + + "), confirmation("+confirmation + + "), event("+event + "), action("+action+"), disabled("+disabled + + "), tabindex("+tabindex+"), conditionGroup("+conditionGroup+")"); + + } + } + + // OHE point of review, to be continue + public void renderResetField(Appendable writer, Map context, ResetField resetField) { + ModelFormField modelFormField = resetField.getModelFormField(); + String name = modelFormField.getParameterName(context); + String className = ""; + String alert = "false"; + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { + className = modelFormField.getWidgetStyle(); + if (modelFormField.shouldBeRed(context)) { + alert = "true"; + } + } + String title = modelFormField.getTitle(context); + Map cb = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(cb, modelFormField, context); + cb.put("className", className); + cb.put("alert", alert); + cb.put("name", name); + cb.put("title", title); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("ResetField", cb); + } + + public void renderHiddenField(Appendable writer, Map context, HiddenField hiddenField) { + ModelFormField modelFormField = hiddenField.getModelFormField(); + String value = hiddenField.getValue(context); + this.renderHiddenField(writer, context, modelFormField, value); + } + + public void renderHiddenField(Appendable writer, Map context, ModelFormField modelFormField, String value) { + String name = modelFormField.getName(); + String action = modelFormField.getAction(context); + String conditionGroup = modelFormField.getConditionGroup(); + String event = modelFormField.getEvent(); + String id = modelFormField.getCurrentContainerId(context); + String formName = FormRenderer.getCurrentFormName(modelFormField.getModelForm(), context); + Map cb = new HashMap<>(); + cb.put("name", name); + cb.put("conditionGroup", conditionGroup); + cb.put("value", value); + cb.put("id", id); + if (event != null) { + cb.put("event", event); + } + if (action != null) { + cb.put("action", action); + } + if (formName != null) { + cb.put("formName", formName); + } + this.output.putScreen("HiddenField", cb); + } + + public void renderIgnoredField(Appendable writer, Map context, IgnoredField ignoredField) { + // do nothing, it's an ignored field; could add a comment or something if we wanted to + } + + public void renderFieldTitle(Appendable writer, Map context, ModelFormField modelFormField) throws IOException { + String titleText = modelFormField.getTitle(context); + String style = modelFormField.getTitleStyle(); + String id = modelFormField.getCurrentContainerId(context); + StringBuilder sb = new StringBuilder(); + if (UtilValidate.isNotEmpty(titleText)) { + if (" ".equals(titleText)) { + this.output.putScreen("FormatEmptySpace", new HashMap<>()); + } else { + titleText = UtilHttp.encodeAmpersands(titleText); + titleText = encode(titleText, modelFormField, context); + if (UtilValidate.isNotEmpty(modelFormField.getHeaderLink())) { + StringBuilder targetBuffer = new StringBuilder(); + FlexibleStringExpander target = FlexibleStringExpander.getInstance(modelFormField.getHeaderLink()); + String fullTarget = target.expandString(context); + targetBuffer.append(fullTarget); + String targetType = CommonWidgetModels.Link.DEFAULT_URL_MODE; + if (UtilValidate.isNotEmpty(targetBuffer.toString()) && targetBuffer.toString().toLowerCase(Locale + .getDefault()).startsWith("javascript:")) { + targetType = "plain"; + } + Map cb = new HashMap<>(); + makeHyperlinkString(cb, modelFormField.getHeaderLinkStyle(), targetType, targetBuffer.toString(), null, titleText, "", modelFormField, this.request, this.response, context, ""); + cb.put("name", modelFormField.getModelForm().getName()); + cb.put("title", cb.remove("HyperlinkString")); + this.output.putScreen("FieldTitle", cb); + } else if (modelFormField.isSortField()) { + renderSortField(writer, context, modelFormField, titleText); + } else if (modelFormField.isRowSubmit()) { // TODO devra être ré-activé + Map cb = new HashMap<>(); + cb.put("name", modelFormField.getModelForm().getName()); + cb.put("title", titleText); + cb.put("showSelectAll", "Y"); + this.output.putScreen("HyperlinkTitle", cb); + } else { + sb.append(titleText); + } + } + } + if (!sb.toString().isEmpty()) { + //check for required field style on single forms + if ("single".equals(modelFormField.getModelForm().getType()) && modelFormField.getRequiredField()) { + String requiredStyle = modelFormField.getRequiredFieldStyle(); + if (UtilValidate.isNotEmpty(requiredStyle)) { + style = requiredStyle; + } + } + Map cb = new HashMap<>(); + cb.put("style", style); + String displayHelpText = UtilProperties.getPropertyValue("widget", "widget.form.displayhelpText"); + if ("Y".equals(displayHelpText)) { + Delegator delegator = WidgetWorker.getDelegator(context); + Locale locale = (Locale) context.get("locale"); + String entityName = modelFormField.getEntityName(); + String fieldName = modelFormField.getFieldName(); + String helpText = UtilHelpText.getEntityFieldDescription(entityName, fieldName, delegator, locale); + + cb.put("fieldHelpText", encodeDoubleQuotes(helpText)); + } + cb.put("title", sb.toString()); + if (UtilValidate.isNotEmpty(id)) { + cb.put("id", id); + cb.put("for", id); + } + if (! "single".equals(modelFormField.getModelForm().getType())) { + this.output.putScreen("FieldTitle", cb); + } + } + } + + public void renderSingleFormFieldTitle(Appendable writer, Map context, ModelFormField modelFormField) throws IOException { + renderFieldTitle(writer, context, modelFormField); + } + + public void renderFormOpen(Appendable writer, Map context, ModelForm modelForm) throws IOException { + String action = null; + Map data = null; + String targetType = modelForm.getTargetType(); + String targ = modelForm.getTarget(context, targetType); + StringBuilder linkUrl = new StringBuilder(); + if (UtilValidate.isNotEmpty(targ)) { + WidgetWorker.buildHyperlinkUrl(linkUrl, targ, targetType, null, null, false, false, true, request, response, context); + } + String formType = modelForm.getType(); + String targetWindow = modelForm.getTargetWindow(context); + String containerId = FormRenderer.getCurrentContainerId(modelForm, context); + String containerStyle = modelForm.getContainerStyle(); + String autocomplete = ""; + String name = FormRenderer.getCurrentFormName(modelForm, context); + String viewIndexField = modelForm.getMultiPaginateIndexField(context); + String viewSizeField = modelForm.getMultiPaginateSizeField(context); + int viewIndex = Paginator.getViewIndex(modelForm, context); + int viewSize = Paginator.getViewSize(modelForm, context); + boolean useRowSubmit = modelForm.getUseRowSubmit(); + if (!modelForm.getClientAutocompleteFields()) { + autocomplete = "off"; + } + String hasRequiredField = ""; + for (ModelFormField formField : modelForm.getFieldList()) { + if (formField.getRequiredField()) { + hasRequiredField = "Y"; + break; + } + } + String focusFieldName = modelForm.getFocusFieldName(); + Map cb = new HashMap<>(); + if (!targ.isEmpty() && !linkUrl.toString().isEmpty()) { + cb.put("linkUrl", linkUrl.toString()); + } else { + cb.put("linkUrl", ""); + } + cb.put("formType", formType); + cb.put("targetWindow", targetWindow); + cb.put("containerId", containerId); + cb.put("containerStyle", containerStyle); + cb.put("autocomplete", autocomplete); + cb.put("name", name); + cb.put("focusFieldName", focusFieldName); + cb.put("hasRequiredField", hasRequiredField); + cb.put("viewIndexField", viewIndexField); + cb.put("viewSizeField", viewSizeField); + cb.put("viewIndex", viewIndex); + cb.put("viewSize", viewSize); + cb.put("useRowSubmit", useRowSubmit); + cb.put("entityName", modelForm.getDefaultEntityName()); +// OH 2019.03.12 à prori non utilisé, s'il faut le réactiver, il faut gérer le format des champs date, cf ExampleFeatureppl +// if (!modelForm.getDefaultMapName().equals("") && ((GenericValue) context.get(modelForm.getDefaultMapName())) != null) { +// cb.put("primaryKey", ((GenericValue) context.get(modelForm.getDefaultMapName())).getPrimaryKey()); +// } + + // Begin data + String defaultEntityName = modelForm.getDefaultEntityName(); + if (UtilValidate.isNotEmpty(defaultEntityName)) { + action = "PUSH_ENTITY"; + data = new HashMap<>(); + data.put("entityName", defaultEntityName); + data.put("primaryKeys", getPkList(defaultEntityName, ((Delegator)context.get("delegator")).getModelReader())); + } + // End data + this.output.pushScreen("FormOpen", cb, action, data); + } + + public void renderFormClose(Appendable writer, Map context, ModelForm modelForm) { +// HashMap hashMapStringObject = new HashMap(); + String action = null; + + // Begin data + String defaultEntityName = modelForm.getDefaultEntityName(); + ModelReader entityModelReader = ((Delegator)context.get("delegator")).getModelReader(); + ModelEntity modelEntity = null; + if (UtilValidate.isNotEmpty(defaultEntityName)) { + try { + modelEntity = entityModelReader.getModelEntity(defaultEntityName); + } catch (GenericEntityException e) { + Debug.logError(e, MODULE); + } + if (modelEntity == null) { + throw new IllegalArgumentException("Error finding Entity with name " + defaultEntityName + + " for defaut-entity-name in a form widget"); + } else { +// List pkList = modelEntity.getPkFieldNames(); + action = "POP_ENTITY"; + } + } + // End data + + this.output.popScreen("FormClose", action); + } + + public void renderMultiFormClose(Appendable writer, Map context, ModelForm modelForm) { + // see if there is anything that needs to be added outside of the multi-form +// Map wholeFormContext = UtilGenerics.checkMap(context.get("wholeFormContext")); +// todo: Have to understand this below +// if (UtilValidate.isNotEmpty(wholeFormContext)) { +// cb.put("wholeFormContext", wholeFormContext); +// } + this.output.popScreen("MultiFormClose"); + + } + + public void renderFormatListWrapperOpen(Appendable writer, Map context, ModelForm modelForm) { + String action = null; + Map data = null; + Map inputFields = UtilGenerics.cast(context.get("requestParameters")); + Object obj = context.get("queryStringMap"); + Map queryStringMap = (obj instanceof Map) ? UtilGenerics.cast(obj) : null; + if (UtilValidate.isNotEmpty(queryStringMap)) { + inputFields.putAll(queryStringMap); + } + if ("multi".equals(modelForm.getType())) { + inputFields = UtilHttp.removeMultiFormParameters(inputFields); + } + String queryString = UtilHttp.urlEncodeArgs(inputFields); + context.put("_QBESTRING_", queryString); + if (this.renderPagination) { + this.renderNextPrev(writer, context, modelForm); + } + List childFieldList = modelForm.getFieldList(); + List columnStyleList = new LinkedList<>(); + List fieldNameList = new LinkedList<>(); + for (ModelFormField childField : childFieldList) { + int childFieldType = childField.getFieldInfo().getFieldType(); + if (childFieldType == FieldInfo.HIDDEN || childFieldType == FieldInfo.IGNORED) { + continue; + } + String areaStyle = childField.getTitleAreaStyle(); + if (UtilValidate.isEmpty(areaStyle)) { + areaStyle = ""; + } + if (fieldNameList.contains(childField.getName())) { + if (UtilValidate.isNotEmpty(areaStyle)) { + columnStyleList.set(fieldNameList.indexOf(childField.getName()), areaStyle); + } + } else { + columnStyleList.add(areaStyle); + fieldNameList.add(childField.getName()); + } + } + String columnStyleListString = + columnStyleList.stream().map(str -> "'" + str + "'").collect(Collectors.joining(", ")); + Map cb = new HashMap<>(); + cb.put("formName", modelForm.getName()); + cb.put("style", FlexibleStringExpander.expandString(modelForm.getDefaultTableStyle(), context)); + if (UtilValidate.isNotEmpty(columnStyleListString)) { + // this is a fix for forms with no fields + cb.put("columnStyles", columnStyleListString); + } + + // Begin data + String defaultEntityName = modelForm.getDefaultEntityName(); + if (UtilValidate.isNotEmpty(defaultEntityName)) { + data = new HashMap<>(); + action = "PUSH_ENTITY"; + data.put("entityName", defaultEntityName); + data.put("primaryKeys", getPkList(defaultEntityName, ((Delegator)context.get("delegator")).getModelReader())); + } else { + //TODO add a log info to explain that store.entity will be not used. Logical when data will not be update by other portlet + } + cb.put("listSize", Paginator.getListSize(context)); + // End data + this.output.pushScreen("ListWrapperOpen", cb, action, data); + } + public void renderEmptyFormDataMessage(Appendable writer, Map context, ModelForm modelForm) { + Map cb = new HashMap<>(); +// cb.put("message", modelForm.getEmptyFormDataMessage(context)); + this.output.putScreen("EmptyFormDataMessage", cb); + } + public void renderFormatListWrapperClose(Appendable writer, Map context, ModelForm modelForm) { + if (UtilValidate.isNotEmpty(modelForm.getDefaultEntityName())) { + this.output.popScreen("ListWrapperClose", "POP_ENTITY"); + } else { + this.output.popScreen("ListWrapperClose"); + } + if (this.renderPagination) { + this.renderNextPrev(writer, context, modelForm); + } + } + + public void renderFormatHeaderOpen(Appendable writer, Map context, ModelForm modelForm) { + this.output.pushScreen("HeaderOpen", new HashMap()); + } + + public void renderFormatHeaderClose(Appendable writer, Map context, ModelForm modelForm) { + this.output.popScreen("HeaderClose"); + } + + public void renderFormatHeaderRowOpen(Appendable writer, Map context, ModelForm modelForm) { + String headerStyle = FlexibleStringExpander.expandString(modelForm.getHeaderRowStyle(), context); + Map cb = new HashMap<>(); + cb.put("style", headerStyle); + this.output.pushScreen("HeaderRowOpen", cb); + } + + public void renderFormatHeaderRowClose(Appendable writer, Map context, ModelForm modelForm) { + this.output.popScreen("HeaderRowClose"); + } + + public void renderFormatHeaderRowCellOpen(Appendable writer, Map context, ModelForm modelForm, ModelFormField modelFormField, int positionSpan) { + String areaStyle = modelFormField.getTitleAreaStyle(); + Map cb = new HashMap<>(); + cb.put("style", areaStyle); + cb.put("positionSpan", positionSpan); + this.output.pushScreen("HeaderRowCellOpen", cb); + } + + public void renderFormatHeaderRowCellClose(Appendable writer, Map context, ModelForm modelForm, ModelFormField modelFormField) { + this.output.popScreen("HeaderRowCellClose"); + } + + public void renderFormatHeaderRowFormCellOpen(Appendable writer, Map context, ModelForm modelForm) { + String areaStyle = modelForm.getFormTitleAreaStyle(); + Map cb = new HashMap<>(); + cb.put("style", areaStyle); + this.output.pushScreen("HeaderRowFormCellOpen", cb); + } + + public void renderFormatHeaderRowFormCellClose(Appendable writer, Map context, ModelForm modelForm) { + this.output.popScreen("HeaderRowFormCellClose"); + } + + public void renderFormatHeaderRowFormCellTitleSeparator(Appendable writer, Map context, ModelForm modelForm, ModelFormField modelFormField, boolean isLast) { + String titleStyle = modelFormField.getTitleStyle(); + Map cb = new HashMap<>(); + cb.put("style", titleStyle); + cb.put("isLast", isLast); + this.output.putScreen("FormatHeaderRowFormCellTitleSeparator", cb); + } + + public void renderFormatItemRowOpen(Appendable writer, Map context, ModelForm modelForm) { + Integer itemIndex = (Integer) context.get("itemIndex"); + String altRowStyles = ""; + String evenRowStyle = ""; + String oddRowStyle = ""; + if (itemIndex != null) { + altRowStyles = modelForm.getStyleAltRowStyle(context); + if (itemIndex % 2 == 0) { + evenRowStyle = modelForm.getEvenRowStyle(); + } else { + oddRowStyle = FlexibleStringExpander.expandString(modelForm.getOddRowStyle(), context); + } + } + Map attributes = new HashMap<>(); + attributes.put("formName", modelForm.getName()); + attributes.put("itemIndex", String.valueOf(itemIndex)); + if (UtilValidate.isNotEmpty(altRowStyles)) attributes.put("altRowStyles", altRowStyles); + if (UtilValidate.isNotEmpty(evenRowStyle)) attributes.put("evenRowStyle", evenRowStyle); + if (UtilValidate.isNotEmpty(oddRowStyle)) attributes.put("oddRowStyle", oddRowStyle); + // //newRecord + this.output.pushScreen("ItemRowOpen", attributes, "NEW_RECORD", context); + } + + public void renderFormatItemRowClose(Appendable writer, Map context, ModelForm modelForm) { + this.output.popScreen("ItemRowClose", "STORE_RECORD"); + } + + public void renderFormatItemRowCellOpen(Appendable writer, Map context, ModelForm modelForm, ModelFormField modelFormField, int positionSpan) { + String areaStyle = modelFormField.getWidgetAreaStyle(); + Map cb = new HashMap<>(); + cb.put("fieldName", modelFormField.getName()); + cb.put("style", areaStyle); + cb.put("positionSpan", positionSpan); + this.output.pushScreen("ItemRowCellOpen", cb); + } + + public void renderFormatItemRowCellClose(Appendable writer, Map context, ModelForm modelForm, ModelFormField modelFormField) { + this.output.popScreen("ItemRowCellClose"); + } + + public void renderFormatItemRowFormCellOpen(Appendable writer, Map context, ModelForm modelForm) { + String areaStyle = modelForm.getFormTitleAreaStyle(); + Map cb = new HashMap<>(); + cb.put("style", areaStyle); + this.output.pushScreen("ItemRowFormCellOpen", cb); + } + + public void renderFormatItemRowFormCellClose(Appendable writer, Map context, ModelForm modelForm) { + this.output.popScreen("ItemRowFormCellClose"); + } + + public void renderFormatSingleWrapperOpen(Appendable writer, Map context, ModelForm modelForm) { + String style = FlexibleStringExpander.expandString(modelForm.getDefaultTableStyle(), context); + Map cb = new HashMap<>(); + cb.put("formName", modelForm.getName()); + cb.put("style", style); + this.output.pushScreen("SingleWrapperOpen", cb, "NEW_RECORD", context); + } + + public void renderFormatSingleWrapperClose(Appendable writer, Map context, ModelForm modelForm) { + this.output.popScreen("SingleWrapperClose", "STORE_RECORD"); + } + + public void renderFormatFieldRowOpen(Appendable writer, Map context, ModelForm modelForm) { + this.output.pushScreen("FieldRowOpen", new HashMap()); + } + + public void renderFormatFieldRowClose(Appendable writer, Map context, ModelForm modelForm) { + this.output.popScreen("FieldRowClose"); + } + + public void renderFormatFieldRowTitleCellOpen(Appendable writer, Map context, ModelFormField modelFormField) { + String style = modelFormField.getTitleAreaStyle(); + Map cb = new HashMap<>(); + if (!style.isEmpty()) cb.put("style", style); + //this.output.pushScreen("FieldRowTitleCellOpen", cb); + } + + public void renderFormatFieldRowTitleCellClose(Appendable writer, Map context, ModelFormField modelFormField) { + //this.output.popScreen("FieldRowTitleCellClose"); + } + + public void renderFormatFieldRowSpacerCell(Appendable writer, Map context, ModelFormField modelFormField) { + } + + public void renderFormatFieldRowWidgetCellOpen(Appendable writer, Map context, ModelFormField modelFormField, int positions, int positionSpan, Integer nextPositionInRow) { + String areaStyle = modelFormField.getWidgetAreaStyle(); + Map cb = new HashMap<>(); + cb.put("positionSpan", positionSpan); + cb.put("style", areaStyle); + //this.output.pushScreen("FieldRowWidgetCellOpen", cb); + } + + public void renderFormatFieldRowWidgetCellClose(Appendable writer, Map context, ModelFormField modelFormField, int positions, int positionSpan, Integer nextPositionInRow) { + //this.output.popScreen("FieldRowWidgetCellClose"); + } + + public void renderFormatEmptySpace(Appendable writer, Map context, ModelForm modelForm) { + this.output.putScreen("FormatEmptySpace", new HashMap<>()); + } + + public void renderTextFindField(Appendable writer, Map context, TextFindField textFindField) { + String fieldName = null; + String fieldValue = null; + ModelFormField modelFormField = textFindField.getModelFormField(); + String defaultOption = textFindField.getDefaultOption(context); + String conditionGroup = modelFormField.getConditionGroup(); + String className = ""; + String alert = "false"; + String opEquals = ""; + String opBeginsWith = ""; + String opContains = ""; + String opIsEmpty = ""; + String opNotEqual = ""; + String name = modelFormField.getName(); + Integer size = textFindField.getSize(); + String maxlength = ""; + String autocomplete = ""; + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { + className = modelFormField.getWidgetStyle(); + if (modelFormField.shouldBeRed(context)) { + alert = "true"; + } + } + Locale locale = (Locale) context.get("locale"); + if (!textFindField.getHideOptions()) { + opEquals = UtilProperties.getMessage("conditionalUiLabels", "equals", locale); + opBeginsWith = UtilProperties.getMessage("conditionalUiLabels", "begins_with", locale); + opContains = UtilProperties.getMessage("conditionalUiLabels", "contains", locale); + opIsEmpty = UtilProperties.getMessage("conditionalUiLabels", "is_empty", locale); + opNotEqual = UtilProperties.getMessage("conditionalUiLabels", "not_equal", locale); + } + String value = modelFormField.getEntry(context, textFindField.getDefaultValue(context)); + if (value == null) { + value = ""; + } + if (textFindField.getMaxlength() != null) { + maxlength = textFindField.getMaxlength().toString(); + } + if (!textFindField.getClientAutocompleteField()) { + autocomplete = "off"; + } + String titleStyle = ""; + if (UtilValidate.isNotEmpty(modelFormField.getTitleStyle())) { + titleStyle = modelFormField.getTitleStyle(); + } + String ignoreCase = UtilProperties.getMessage("conditionalUiLabels", "ignore_case", locale); + boolean ignCase = textFindField.getIgnoreCase(context); + boolean hideIgnoreCase = textFindField.getHideIgnoreCase(); + String tabindex = modelFormField.getTabindex(); + String formName = modelFormField.getModelForm().getName(); + Map cb = new HashMap<>(); + this.addTitle(cb, modelFormField, context); + cb.put("name", name); + cb.put("value", value); + cb.put("defaultOption", defaultOption); + cb.put("opEquals", opEquals); + cb.put("opBeginsWith", opBeginsWith); + cb.put("opContains", opContains); + cb.put("opIsEmpty", opIsEmpty); + cb.put("opNotEqual", opNotEqual); + cb.put("className", className); + cb.put("alert", alert); + cb.put("size", size); + cb.put("maxlength", maxlength); + cb.put("autocomplete", autocomplete); + cb.put("titleStyle", titleStyle); + cb.put("hideIgnoreCase", hideIgnoreCase); + cb.put("ignCase", ignCase); + cb.put("ignoreCase", ignoreCase); + cb.put("tabindex", tabindex); + cb.put("conditionGroup", conditionGroup); + cb.put("formName", formName); + Map data = new HashMap<>(); + fieldName = name; + fieldValue = value; + // TODO check if recordPointer is used, should read VueTextFindField.vue to check. + Map pointer = output.getRecordPointer(context); + if (pointer != null) { + pointer.put("field", name); + data.put("recordPointer", pointer); + } + cb.put("data", data); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("TextFindField", cb, fieldName, fieldValue); + } + + public void renderRangeFindField(Appendable writer, Map context, RangeFindField rangeFindField) { + ModelFormField modelFormField = rangeFindField.getModelFormField(); + Locale locale = (Locale) context.get("locale"); + String opEquals = UtilProperties.getMessage("conditionalUiLabels", "equals", locale); + String opGreaterThan = UtilProperties.getMessage("conditionalUiLabels", "greater_than", locale); + String opGreaterThanEquals = UtilProperties.getMessage("conditionalUiLabels", "greater_than_equals", locale); + String opLessThan = UtilProperties.getMessage("conditionalUiLabels", "less_than", locale); + String opLessThanEquals = UtilProperties.getMessage("conditionalUiLabels", "less_than_equals", locale); + String conditionGroup = modelFormField.getConditionGroup(); + String className = ""; + String alert = "false"; + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { + className = modelFormField.getWidgetStyle(); + if (modelFormField.shouldBeRed(context)) { + alert = "true"; + } + } + String name = modelFormField.getParameterName(context); + Integer size = rangeFindField.getSize(); + String value = modelFormField.getEntry(context, rangeFindField.getDefaultValue(context)); + if (value == null) { + value = ""; + } + Integer maxlength = rangeFindField.getMaxlength(); + String autocomplete = ""; + + if (!rangeFindField.getClientAutocompleteField()) { + autocomplete = "off"; + } + String titleStyle = modelFormField.getTitleStyle(); + + if (titleStyle == null) { + titleStyle = ""; + } + String defaultOptionFrom = rangeFindField.getDefaultOptionFrom(); + String value2 = modelFormField.getEntry(context); + if (value2 == null) { + value2 = ""; + } + String defaultOptionThru = rangeFindField.getDefaultOptionThru(); + String tabindex = modelFormField.getTabindex(); + Map cb = new HashMap<>(); + cb.put("className", className); + cb.put("alert", alert); + cb.put("name", name); + cb.put("value", value); + cb.put("size", size); + if (maxlength != null) { + cb.put("maxlength", maxlength); + } + cb.put("autocomplete", autocomplete); + cb.put("titleStyle", titleStyle); + cb.put("defaultOptionFrom", defaultOptionFrom); + cb.put("opEquals", opEquals); + cb.put("opGreaterThan", opGreaterThan); + cb.put("opGreaterThanEquals", opGreaterThanEquals); + cb.put("opLessThan", opLessThan); + cb.put("opLessThanEquals", opLessThanEquals); + cb.put("value2", value2); + cb.put("defaultOptionThru", defaultOptionThru); + cb.put("conditionGroup", conditionGroup); + cb.put("tabindex", tabindex); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("RangeFindField", cb); + } + + public void renderDateFindField(Appendable writer, Map context, DateFindField dateFindField) { + ModelFormField modelFormField = dateFindField.getModelFormField(); + Locale locale = (Locale) context.get("locale"); + String opEquals = UtilProperties.getMessage("conditionalUiLabels", "equals", locale); + String opGreaterThan = UtilProperties.getMessage("conditionalUiLabels", "greater_than", locale); + String opSameDay = UtilProperties.getMessage("conditionalUiLabels", "same_day", locale); + String opGreaterThanFromDayStart = UtilProperties.getMessage("conditionalUiLabels", "greater_than_from_day_start", locale); + String opLessThan = UtilProperties.getMessage("conditionalUiLabels", "less_than", locale); + String opUpToDay = UtilProperties.getMessage("conditionalUiLabels", "up_to_day", locale); + String opUpThruDay = UtilProperties.getMessage("conditionalUiLabels", "up_thru_day", locale); + String opIsEmpty = UtilProperties.getMessage("conditionalUiLabels", "is_empty", locale); + String conditionGroup = modelFormField.getConditionGroup(); + Map uiLabelMap = UtilGenerics.cast(context.get("uiLabelMap")); + if (uiLabelMap == null) { + Debug.logWarning("Could not find uiLabelMap in context", MODULE); + } + String localizedInputTitle = "", localizedIconTitle = ""; + String className = ""; + String alert = "false"; + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { + className = modelFormField.getWidgetStyle(); + if (modelFormField.shouldBeRed(context)) { + alert = "true"; + } + } + String name = modelFormField.getParameterName(context); + // the default values for a timestamp + int size = 25; + int maxlength = 30; + String dateType = dateFindField.getType(); + if ("date".equals(dateType)) { + size = maxlength = 10; + if (uiLabelMap != null) { + localizedInputTitle = uiLabelMap.get("CommonFormatDate"); + } + } else if ("time".equals(dateFindField.getType())) { + size = maxlength = 8; + if (uiLabelMap != null) { + localizedInputTitle = uiLabelMap.get("CommonFormatTime"); + } + } else { + if (uiLabelMap != null) { + localizedInputTitle = uiLabelMap.get("CommonFormatDateTime"); + } + } + String value = modelFormField.getEntry(context, dateFindField.getDefaultValue(context)); + if (value == null) { + value = ""; + } + // search for a localized label for the icon + if (uiLabelMap != null) { + localizedIconTitle = uiLabelMap.get("CommonViewCalendar"); + } + String formName = ""; + String defaultDateTimeString = ""; + String imgSrc = ""; + // add calendar pop-up button and seed data IF this is not a "time" type date-find + if (!"time".equals(dateFindField.getType())) { + ModelForm modelForm = modelFormField.getModelForm(); + formName = FormRenderer.getCurrentFormName(modelForm, context); + defaultDateTimeString = UtilHttp.encodeBlanks(modelFormField.getEntry(context, dateFindField.getDefaultDateTimeString(context))); + imgSrc = this.appendContentUrl("/images/cal.gif"); + } + String defaultOptionFrom = dateFindField.getDefaultOptionFrom(context); + String defaultOptionThru = dateFindField.getDefaultOptionThru(context); + String value2 = modelFormField.getEntry(context); + if (value2 == null) { + value2 = ""; + } + if (context.containsKey("parameters")) { + Map parameters = UtilGenerics.cast(context.get("parameters")); + if (parameters.containsKey(name + "_fld0_value")) { + value = (String) parameters.get(name + "_fld0_value"); + } + if (parameters.containsKey(name + "_fld1_value")) { + value2 = (String) parameters.get(name + "_fld1_value"); + } + } + + String titleStyle = ""; + if (UtilValidate.isNotEmpty(modelFormField.getTitleStyle())) { + titleStyle = modelFormField.getTitleStyle(); + } + String tabindex = modelFormField.getTabindex(); + Map cb = new HashMap<>(); + cb.put("className", className); + cb.put("alert", alert); + cb.put("name", name); + cb.put("localizedInputTitle", localizedInputTitle); + cb.put("value", value); + cb.put("value2", value2); + cb.put("size", size); + cb.put("maxlength", maxlength); + cb.put("dateType", dateType); + cb.put("formName", formName); + cb.put("defaultDateTimeString", defaultDateTimeString); + cb.put("imgSrc", imgSrc); + cb.put("conditionGroup", conditionGroup); + cb.put("localizedIconTitle", localizedIconTitle); + cb.put("titleStyle", titleStyle); + cb.put("defaultOptionFrom", defaultOptionFrom); + cb.put("defaultOptionThru", defaultOptionThru); + cb.put("opEquals", opEquals); + cb.put("opSameDay", opSameDay); + cb.put("opGreaterThanFromDayStart", opGreaterThanFromDayStart); + cb.put("opGreaterThan", opGreaterThan); + cb.put("opGreaterThan", opGreaterThan); + cb.put("opLessThan", opLessThan); + cb.put("opUpToDay", opUpToDay); + cb.put("opUpThruDay", opUpThruDay); + cb.put("opIsEmpty", opIsEmpty); + cb.put("tabindex", tabindex); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("DateFindField", cb); + } + + public void renderLookupField(Appendable writer, Map context, LookupField lookupField) { + ModelFormField modelFormField = lookupField.getModelFormField(); + String lookupFieldFormName = lookupField.getFormName(context); + String conditionGroup = modelFormField.getConditionGroup(); + String className = ""; + String alert = "false"; + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { + className = modelFormField.getWidgetStyle(); + if (modelFormField.shouldBeRed(context)) { + alert = "true"; + } + } + //check for required field style on single forms + if ("single".equals(modelFormField.getModelForm().getType()) && modelFormField.getRequiredField()) { + String requiredStyle = modelFormField.getRequiredFieldStyle(); + if (UtilValidate.isEmpty(requiredStyle)) { + requiredStyle = "required"; + } + if (UtilValidate.isEmpty(className)) { + className = requiredStyle; + } else { + className = requiredStyle + " " + className; + } + } + String name = modelFormField.getParameterName(context); + String value = modelFormField.getEntry(context, lookupField.getDefaultValue(context)); + if (value == null) { + value = ""; + } + Integer size = lookupField.getSize(); + Integer maxlength = lookupField.getMaxlength(); + String id = modelFormField.getCurrentContainerId(context); + List updateAreas = modelFormField.getOnChangeUpdateAreas(); + //add default ajax auto completer to all lookup fields + if (UtilValidate.isEmpty(updateAreas) && UtilValidate.isNotEmpty(lookupFieldFormName)) { + String autoCompleterTarget = null; + if (lookupFieldFormName.indexOf('?') == -1) { + autoCompleterTarget = lookupFieldFormName + "?"; + } else { + autoCompleterTarget = lookupFieldFormName + "&amp;"; + } + autoCompleterTarget = autoCompleterTarget + "ajaxLookup=Y"; + updateAreas = new LinkedList<>(); + updateAreas.add(new UpdateArea("change", id, autoCompleterTarget)); + } + boolean ajaxEnabled = UtilValidate.isNotEmpty(updateAreas) && this.javaScriptEnabled; + String autocomplete = ""; + if (!lookupField.getClientAutocompleteField() || !ajaxEnabled) { + autocomplete = "off"; + } + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + boolean readonly = lookupField.getReadonly(); + // add lookup pop-up button + String descriptionFieldName = lookupField.getDescriptionFieldName(); + ModelForm modelForm = modelFormField.getModelForm(); + String formName = modelFormField.getParentFormName(); + if (UtilValidate.isEmpty(formName)) { + formName = FormRenderer.getCurrentFormName(modelForm, context); + } + String imgSrc = ""; + List targetParameterList = lookupField.getTargetParameterList(context); + /* OH 13.12.2019 simplification for targetParameter + // FIXME: refactor using the StringUtils methods + StringBuilder targetParameterIter = new StringBuilder(); + targetParameterIter.append("["); + for (String targetParameter : targetParameterList) { + if (targetParameterIter.length() > 1) { + targetParameterIter.append(","); + } + targetParameterIter.append("'"); + targetParameterIter.append(targetParameter); + targetParameterIter.append("'"); + } + targetParameterIter.append("]"); + */ + imgSrc = this.appendContentUrl("/images/fieldlookup.gif"); + String ajaxUrl = ""; + if (ajaxEnabled) { + ajaxUrl = createAjaxParamsFromUpdateAreas(updateAreas, "", context); + } + String lookupPresentation = lookupField.getLookupPresentation(); + if (UtilValidate.isEmpty(lookupPresentation)) { + lookupPresentation = ""; + } + String lookupHeight = lookupField.getLookupHeight(); + String lookupWidth = lookupField.getLookupWidth(); + String lookupPosition = lookupField.getLookupPosition(); + String fadeBackground = lookupField.getFadeBackground(); + if (UtilValidate.isEmpty(fadeBackground)) { + fadeBackground = "false"; + } + Boolean isInitiallyCollapsed = lookupField.getInitiallyCollapsed(); + String clearText = ""; + Map uiLabelMap = UtilGenerics.cast(context.get("uiLabelMap")); + if (uiLabelMap != null) { + clearText = (String) uiLabelMap.get("CommonClear"); + } else { + Debug.logWarning("Could not find uiLabelMap in context", MODULE); + } + Boolean showDescription = lookupField.getShowDescription(); + if (showDescription == null) { + showDescription = "Y".equals(visualTheme.getModelTheme().getLookupShowDescription()); + } + // lastViewName, used by lookup to remember the real last view name + String lastViewName = request.getParameter("_LAST_VIEW_NAME_"); // Try to get it from parameters firstly + if (UtilValidate.isEmpty(lastViewName)) { // get from session + lastViewName = (String) request.getSession().getAttribute("_LAST_VIEW_NAME_"); + } + if (UtilValidate.isEmpty(lastViewName)) { + lastViewName = ""; + } + String tabindex = modelFormField.getTabindex(); + Map cb = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(cb, modelFormField, context); + cb.put("className", className); + cb.put("alert", alert); + cb.put("name", name); + cb.put("value", value); + cb.put("size", size); + cb.put("maxlength", maxlength != null ? maxlength : ""); + cb.put("id", id); + if (event != null) { + cb.put("event", event); + } + if (action != null) { + cb.put("action", action); + } + cb.put("readonly", readonly); + cb.put("autocomplete", autocomplete); + cb.put("descriptionFieldName", descriptionFieldName); + cb.put("formName", formName); + cb.put("fieldFormName", lookupFieldFormName); + cb.put("targetParameters", targetParameterList); + cb.put("imgSrc", imgSrc); + cb.put("ajaxUrl", ajaxUrl); + cb.put("ajaxEnabled", ajaxEnabled); + cb.put("presentation", lookupPresentation); + if (UtilValidate.isNotEmpty(lookupHeight)) { + cb.put("height", lookupHeight); + } + if (UtilValidate.isNotEmpty(lookupWidth)) { + cb.put("width", lookupWidth); + } + if (UtilValidate.isNotEmpty(lookupPosition)) { + cb.put("position", lookupPosition); + } + cb.put("fadeBackground", fadeBackground); + cb.put("clearText", clearText); + cb.put("showDescription", showDescription); + cb.put("initiallyCollapsed", isInitiallyCollapsed); + cb.put("lastViewName", lastViewName); + cb.put("conditionGroup", conditionGroup); + cb.put("tabindex", tabindex); + cb.put("delegatorName", ((HttpSession)context.get("session")).getAttribute("delegatorName").toString()); + this.addAsterisks(cb, context, modelFormField); + this.appendTooltip(cb, context, modelFormField); + this.makeHyperlinkString(cb, lookupField.getSubHyperlink(), context); + + this.output.putScreen("LookupField", cb, name, value); + +// // OH Temporary code, send only attributes which are understand and manage in vueJs +// Map attributes = new HashMap<>(); +// attributes.put("formName", formName); +// attributes.put("name", name); +// attributes.put("value", value); +// attributes.put("id", modelFormField.getCurrentContainerId(context)); +// attributes.put("fieldFormName", lookupFieldFormName); +// attributes.put("size", lookupField.getSize()); +// this.output.putScreen("LookupField", attributes, name, value); + } + + public void renderNextPrev(Appendable writer, Map context, ModelForm modelForm) { + boolean ajaxEnabled = false; + List updateAreas = modelForm.getOnPaginateUpdateAreas(); + String targetService = modelForm.getPaginateTarget(context); + if (this.javaScriptEnabled) { + if (UtilValidate.isNotEmpty(updateAreas)) { + ajaxEnabled = true; + } + } + if (targetService == null) { + targetService = "${targetService}"; + } + if (UtilValidate.isEmpty(targetService) && updateAreas == null) { + Debug.logWarning("Cannot paginate because TargetService is empty for the form: " + modelForm.getName(), MODULE); + return; + } + // get the parameterized pagination index and size fields + int paginatorNumber = WidgetWorker.getPaginatorNumber(context); + String viewIndexParam = modelForm.getMultiPaginateIndexField(context); + String viewSizeParam = modelForm.getMultiPaginateSizeField(context); + int viewIndex = Paginator.getViewIndex(modelForm, context); + int viewSize = Paginator.getViewSize(modelForm, context); + int listSize = Paginator.getListSize(context); + int lowIndex = Paginator.getLowIndex(context); + int highIndex = Paginator.getHighIndex(context); + int actualPageSize = Paginator.getActualPageSize(context); + // needed for the "Page" and "rows" labels + Map uiLabelMap = UtilGenerics.cast(context.get("uiLabelMap")); + String pageLabel = ""; + String commonDisplaying = ""; + if (uiLabelMap == null) { + Debug.logWarning("Could not find uiLabelMap in context", MODULE); + } else { + pageLabel = uiLabelMap.get("CommonPage"); + Map messageMap = UtilMisc.toMap("lowCount", lowIndex + 1, "highCount", lowIndex + actualPageSize, "total", Integer.valueOf(listSize)); + commonDisplaying = UtilProperties.getMessage("CommonUiLabels", "CommonDisplaying", messageMap, (Locale) context.get("locale")); + } + // for legacy support, the viewSizeParam is VIEW_SIZE and viewIndexParam is VIEW_INDEX when the fields are "viewSize" and "viewIndex" + if (("viewIndex" + "_" + paginatorNumber).equals(viewIndexParam)) { + viewIndexParam = "VIEW_INDEX" + "_" + paginatorNumber; + } + if (("viewSize" + "_" + paginatorNumber).equals(viewSizeParam)) { + viewSizeParam = "VIEW_SIZE" + "_" + paginatorNumber; + } + String str = (String) context.get("_QBESTRING_"); + // strip legacy viewIndex/viewSize params from the query string + String queryString = UtilHttp.stripViewParamsFromQueryString(str, "" + paginatorNumber); + // strip parameterized index/size params from the query string + Set paramNames = new HashSet<>(); + paramNames.add(viewIndexParam); + paramNames.add(viewSizeParam); + queryString = UtilHttp.stripNamedParamsFromQueryString(queryString, paramNames); + String anchor = ""; + String paginateAnchor = modelForm.getPaginateTargetAnchor(); + if (UtilValidate.isNotEmpty(paginateAnchor)) { + anchor = "#" + paginateAnchor; + } + // Create separate url path String and request parameters String, + // add viewIndex/viewSize parameters to request parameter String + String urlPath = UtilHttp.removeQueryStringFromTarget(targetService); + String prepLinkText = UtilHttp.getQueryStringFromTarget(targetService); + String prepLinkSizeText; + if (UtilValidate.isNotEmpty(queryString)) { + queryString = UtilHttp.encodeAmpersands(queryString); + } + if (prepLinkText.indexOf('?') < 0) { + prepLinkText += "?"; + } else if (!prepLinkText.endsWith("?")) { + prepLinkText += "&"; + } + if (UtilValidate.isNotEmpty(queryString) && !"null".equals(queryString)) { + prepLinkText += queryString + "&"; + } + prepLinkSizeText = prepLinkText + viewSizeParam + "='+this.value+'" + "&" + viewIndexParam + "=0"; + prepLinkText += viewSizeParam + "=" + viewSize + "&" + viewIndexParam + "="; + if (ajaxEnabled) { + // Prepare params for prototype.js + prepLinkText = prepLinkText.replace("?", ""); + prepLinkText = prepLinkText.replace("&", "&"); + } + String linkText; + String paginateStyle = modelForm.getPaginateStyle(); + String paginateFirstStyle = modelForm.getPaginateFirstStyle(); + String paginateFirstLabel = modelForm.getPaginateFirstLabel(context); + String firstUrl = ""; + String ajaxFirstUrl = ""; + String paginatePreviousStyle = modelForm.getPaginatePreviousStyle(); + String paginatePreviousLabel = modelForm.getPaginatePreviousLabel(context); + String previousUrl = ""; + String ajaxPreviousUrl = ""; + String selectUrl = ""; + String ajaxSelectUrl = ""; + String paginateViewSizeLabel = modelForm.getPaginateViewSizeLabel(context); + String selectSizeUrl = ""; + String ajaxSelectSizeUrl = ""; + String paginateNextStyle = modelForm.getPaginateNextStyle(); + String paginateNextLabel = modelForm.getPaginateNextLabel(context); + String nextUrl = ""; + String ajaxNextUrl = ""; + String paginateLastStyle = modelForm.getPaginateLastStyle(); + String paginateLastLabel = modelForm.getPaginateLastLabel(context); + String lastUrl = ""; + String ajaxLastUrl = ""; + if (viewIndex > 0) { + if (ajaxEnabled) { + ajaxFirstUrl = createAjaxParamsFromUpdateAreas(updateAreas, prepLinkText + 0 + anchor, context); + } else { + linkText = prepLinkText + 0 + anchor; + firstUrl = rh.makeLink(this.request, this.response, urlPath + linkText); + } + } + if (viewIndex > 0) { + if (ajaxEnabled) { + ajaxPreviousUrl = createAjaxParamsFromUpdateAreas(updateAreas, prepLinkText + (viewIndex - 1) + anchor, context); + } else { + linkText = prepLinkText + (viewIndex - 1) + anchor; + previousUrl = rh.makeLink(this.request, this.response, urlPath + linkText); + } + } + // Page select dropdown + if (listSize > 0 && this.javaScriptEnabled) { + if (ajaxEnabled) { + ajaxSelectUrl = createAjaxParamsFromUpdateAreas(updateAreas, prepLinkText + "' + this.value + '", context); + } else { + linkText = prepLinkText; + if (linkText.startsWith("/")) { + linkText = linkText.substring(1); + } + selectUrl = rh.makeLink(this.request, this.response, urlPath + linkText); + } + } + // Next button + if (highIndex < listSize) { + if (ajaxEnabled) { + ajaxNextUrl = createAjaxParamsFromUpdateAreas(updateAreas, prepLinkText + (viewIndex + 1) + anchor, context); + } else { + linkText = prepLinkText + (viewIndex + 1) + anchor; + nextUrl = rh.makeLink(this.request, this.response, urlPath + linkText); + } + } + // Last button + if (highIndex < listSize) { + int lastIndex = UtilMisc.getViewLastIndex(listSize, viewSize); + if (ajaxEnabled) { + ajaxLastUrl = createAjaxParamsFromUpdateAreas(updateAreas, prepLinkText + lastIndex + anchor, context); + } else { + linkText = prepLinkText + lastIndex + anchor; + lastUrl = rh.makeLink(this.request, this.response, urlPath + linkText); + } + } + // Page size select dropdown + if (listSize > 0 && this.javaScriptEnabled) { + if (ajaxEnabled) { + ajaxSelectSizeUrl = createAjaxParamsFromUpdateAreas(updateAreas, prepLinkSizeText + anchor, context); + } else { + linkText = prepLinkSizeText; + if (linkText.startsWith("/")) { + linkText = linkText.substring(1); + } + selectSizeUrl = rh.makeLink(this.request, this.response, urlPath + linkText); + } + } + Map cb = new HashMap<>(); + cb.put("paginateStyle", paginateStyle); + cb.put("paginateFirstStyle", paginateFirstStyle); + cb.put("viewIndex", viewIndex); + cb.put("highIndex", highIndex); + cb.put("listSize", listSize); + cb.put("viewSize", viewSize); + cb.put("ajaxEnabled", ajaxEnabled); + cb.put("javaScriptEnabled", javaScriptEnabled); + cb.put("ajaxFirstUrl", ajaxFirstUrl); + cb.put("firstUrl", firstUrl); + cb.put("paginateFirstLabel", paginateFirstLabel); + cb.put("paginatePreviousStyle", paginatePreviousStyle); + cb.put("ajaxPreviousUrl", ajaxPreviousUrl); + cb.put("previousUrl", previousUrl); + cb.put("paginatePreviousLabel", paginatePreviousLabel); + cb.put("pageLabel", pageLabel); + cb.put("ajaxSelectUrl", ajaxSelectUrl); + cb.put("selectUrl", selectUrl); + cb.put("ajaxSelectSizeUrl", ajaxSelectSizeUrl); + cb.put("selectSizeUrl", selectSizeUrl); + cb.put("commonDisplaying", commonDisplaying); + cb.put("paginateNextStyle", paginateNextStyle); + cb.put("ajaxNextUrl", ajaxNextUrl); + cb.put("nextUrl", nextUrl); + cb.put("paginateNextLabel", paginateNextLabel); + cb.put("paginateLastStyle", paginateLastStyle); + cb.put("ajaxLastUrl", ajaxLastUrl); + cb.put("lastUrl", lastUrl); + cb.put("paginateLastLabel", paginateLastLabel); + cb.put("paginateViewSizeLabel", paginateViewSizeLabel); + cb.put("paginateTarget", modelForm.getPaginateTarget()); + this.output.putScreen("NextPrev", cb); + } + + public void renderFileField(Appendable writer, Map context, FileField textField) { + ModelFormField modelFormField = textField.getModelFormField(); + String className = ""; + String alert = "false"; + String name = modelFormField.getParameterName(context); + String value = modelFormField.getEntry(context, textField.getDefaultValue(context)); + Integer size = textField.getSize(); + String maxlength = ""; + String autocomplete = ""; + String formName = textField.getModelFormField().getModelForm().getName(); + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { + className = modelFormField.getWidgetStyle(); + if (modelFormField.shouldBeRed(context)) { + alert = "true"; + } + } + if (UtilValidate.isEmpty(value)) { + value = ""; + } + if (textField.getMaxlength() != null) { + maxlength = textField.getMaxlength().toString(); + } + if (!textField.getClientAutocompleteField()) { + autocomplete = "off"; + } + String tabindex = modelFormField.getTabindex(); + Map cb = new HashMap<>(); + cb.put("className", className); + cb.put("alert", alert); + cb.put("name", name); + cb.put("value", value); + cb.put("size", size); + cb.put("maxlength", maxlength); + cb.put("autocomplete", autocomplete); + cb.put("tabindex", tabindex); + cb.put("formName", formName); + this.makeHyperlinkString(cb, textField.getSubHyperlink(), context); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("FileField", cb); + } + + public void renderPasswordField(Appendable writer, Map context, PasswordField passwordField) { + ModelFormField modelFormField = passwordField.getModelFormField(); + String className = ""; + String alert = "false"; + String name = modelFormField.getParameterName(context); + Integer size = passwordField.getSize(); + String maxlength = ""; + String id = modelFormField.getCurrentContainerId(context); + String autocomplete = ""; + String formName = passwordField.getModelFormField().getModelForm().getName(); + if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { + className = modelFormField.getWidgetStyle(); + if (modelFormField.shouldBeRed(context)) { + alert = "true"; + } + } + String value = modelFormField.getEntry(context, passwordField.getDefaultValue(context)); + if (value == null) { + value = ""; + } + if (passwordField.getMaxlength() != null) { + maxlength = passwordField.getMaxlength().toString(); + } + if (id == null) { + id = ""; + } + if (!passwordField.getClientAutocompleteField()) { + autocomplete = "off"; + } + + //check for required field style on single forms + if ("single".equals(modelFormField.getModelForm().getType()) && modelFormField.getRequiredField()) { + String requiredStyle = modelFormField.getRequiredFieldStyle(); + if (UtilValidate.isEmpty(requiredStyle)) { + requiredStyle = "required"; + } + if (UtilValidate.isEmpty(className)) { + className = requiredStyle; + } else { + className = requiredStyle + " " + className; + } + } + + String tabindex = modelFormField.getTabindex(); + Map cb = new HashMap<>(); + if ("single".equals(modelFormField.getModelForm().getType())) this.addTitle(cb, modelFormField, context); + + cb.put("className", className); + cb.put("alert", alert); + cb.put("name", name); + cb.put("value", value); + cb.put("size", size); + cb.put("maxlength", maxlength); + cb.put("id", id); + cb.put("autocomplete", autocomplete); + cb.put("tabindex", tabindex); + cb.put("formName", formName); + this.addAsterisks(cb, context, modelFormField); + this.makeHyperlinkString(cb, passwordField.getSubHyperlink(), context); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("PasswordField", cb); + } + + public void renderImageField(Appendable writer, Map context, ImageField imageField) { + ModelFormField modelFormField = imageField.getModelFormField(); + String value = modelFormField.getEntry(context, imageField.getValue(context)); + String description = imageField.getDescription(context); + String alternate = imageField.getAlternate(context); + String style = imageField.getStyle(context); + if (UtilValidate.isEmpty(description)) { + description = imageField.getModelFormField().getTitle(context); + } + if (UtilValidate.isEmpty(alternate)) { + alternate = description; + } + if (UtilValidate.isNotEmpty(value)) { + if (!value.startsWith("http")) { + StringBuilder buffer = new StringBuilder(); + ContentUrlTag.appendContentPrefix(request, buffer); + buffer.append(value); + value = buffer.toString(); + } + } else if (value == null) { + value = ""; + } + String event = modelFormField.getEvent(); + String action = modelFormField.getAction(context); + Map cb = new HashMap<>(); + + cb.put("value", value); + cb.put("description", encode(description, modelFormField, context)); + cb.put("alternate", encode(alternate, modelFormField, context)); + cb.put("style", style); + cb.put("event", event == null ? "" : event); + cb.put("action", action == null ? "" : action); + this.makeHyperlinkString(cb, imageField.getSubHyperlink(), context); + this.appendTooltip(cb, context, modelFormField); + this.output.putScreen("ImageField", cb); + } + + public void renderFieldGroupOpen(Appendable writer, Map context, ModelForm.FieldGroup fieldGroup) { + String style = fieldGroup.getStyle(); + String id = fieldGroup.getId(); + FlexibleStringExpander titleNotExpanded = FlexibleStringExpander.getInstance(fieldGroup.getTitle()); + String title = titleNotExpanded.expandString(context); + Boolean collapsed = fieldGroup.initiallyCollapsed(); + String collapsibleAreaId = fieldGroup.getId() + "_body"; + Boolean collapsible = fieldGroup.collapsible(); + String expandToolTip = ""; + String collapseToolTip = ""; + if (UtilValidate.isNotEmpty(style) || UtilValidate.isNotEmpty(id) || UtilValidate.isNotEmpty(title)) { + if (fieldGroup.collapsible()) { + Map uiLabelMap = UtilGenerics.cast(context.get("uiLabelMap")); + if (uiLabelMap != null) { + expandToolTip = (String) uiLabelMap.get("CommonExpand"); + collapseToolTip = (String) uiLabelMap.get("CommonCollapse"); + } + } + } + Map cb = new HashMap<>(); + + if (style != null) { + cb.put("style", style); + } + cb.put("id", id); + cb.put("title", title); + cb.put("collapsed", collapsed); + cb.put("collapsibleAreaId", collapsibleAreaId); + cb.put("collapsible", collapsible); + cb.put("expandToolTip", expandToolTip); + cb.put("collapseToolTip", collapseToolTip); + this.output.pushScreen("FieldGroupOpen", cb); + } + + public void renderFieldGroupClose(Appendable writer, Map context, ModelForm.FieldGroup fieldGroup) { +// todo +// String style = fieldGroup.getStyle(); +// String id = fieldGroup.getId(); +// FlexibleStringExpander titleNotExpanded = FlexibleStringExpander.getInstance(fieldGroup.getTitle()); +// String title = titleNotExpanded.expandString(context); +// Map cb = new HashMap<>(); +// +// if (style != null) { +// cb.put("style", style); +// } +// if (id != null) { +// cb.put("id", id); +// } +// if (title != null) { +// cb.put("title", title); +// } + this.output.popScreen("FieldGroupClose"); + } + + public void renderBanner(Appendable writer, Map context, ModelForm.Banner banner) { + String style = banner.getStyle(context); + String leftStyle = banner.getLeftTextStyle(context); + if (UtilValidate.isEmpty(leftStyle)) { + leftStyle = style; + } + String rightStyle = banner.getRightTextStyle(context); + if (UtilValidate.isEmpty(rightStyle)) { + rightStyle = style; + } + String leftText = banner.getLeftText(context); + if (leftText == null) { + leftText = ""; + } + String text = banner.getText(context); + if (text == null) { + text = ""; + } + String rightText = banner.getRightText(context); + if (rightText == null) { + rightText = ""; + } + Map cb = new HashMap<>(); + + cb.put("style", style); + cb.put("leftStyle", leftStyle); + cb.put("rightStyle", rightStyle); + cb.put("leftText", leftText); + cb.put("text", text); + cb.put("rightText", rightText); + this.output.putScreen("Banner", cb); + } + + + private void renderSortField(Appendable writer, Map context, ModelFormField modelFormField, String titleText) { + boolean ajaxEnabled = false; + ModelForm modelForm = modelFormField.getModelForm(); + List updateAreas = modelForm.getOnSortColumnUpdateAreas(); + if (updateAreas == null) { + // For backward compatibility. + updateAreas = modelForm.getOnPaginateUpdateAreas(); + } + if (this.javaScriptEnabled) { + if (UtilValidate.isNotEmpty(updateAreas)) { + ajaxEnabled = true; + } + } + String paginateTarget = modelForm.getPaginateTarget(context); + if (paginateTarget.isEmpty() && updateAreas == null) { + Debug.logWarning("Cannot sort because the paginate target URL is empty for the form: " + modelForm.getName(), MODULE); + return; + } + String oldSortField = modelForm.getSortField(context); + String sortFieldStyle = modelFormField.getSortFieldStyle(); + // if the entry-name is defined use this instead of field name + String columnField = modelFormField.getEntryName(); + if (UtilValidate.isEmpty(columnField)) { + columnField = modelFormField.getFieldName(); + } + // switch between asc/desc order + String newSortField = columnField; + if (UtilValidate.isNotEmpty(oldSortField)) { + if (oldSortField.equals(columnField)) { + newSortField = "-" + columnField; + sortFieldStyle = modelFormField.getSortFieldStyleDesc(); + } else if (("-" + columnField).equals(oldSortField)) { + newSortField = columnField; + sortFieldStyle = modelFormField.getSortFieldStyleAsc(); + } + } + String queryString = UtilHttp.getQueryStringFromTarget(paginateTarget).replace("?", ""); + Map paramMap = UtilHttp.getQueryStringOnlyParameterMap(queryString); + String qbeString = (String) context.get("_QBESTRING_"); + if (qbeString != null) { + qbeString = qbeString.replaceAll("&", "&"); + paramMap.putAll(UtilHttp.getQueryStringOnlyParameterMap(qbeString)); + } + paramMap.put(modelForm.getSortFieldParameterName(), newSortField); + UtilHttp.canonicalizeParameterMap(paramMap); + String linkUrl; + if (ajaxEnabled) { + linkUrl = createAjaxParamsFromUpdateAreas(updateAreas, paramMap, null, context); + } else { + StringBuilder sb = new StringBuilder("?"); + Iterator> iter = paramMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + sb.append(entry.getKey()).append("=").append(entry.getValue()); + if (iter.hasNext()) { + sb.append("&"); + } + } + String newQueryString = sb.toString(); + String urlPath = UtilHttp.removeQueryStringFromTarget(paginateTarget); + linkUrl = rh.makeLink(this.request, this.response, urlPath.concat(newQueryString)); + } + Map cb = new HashMap<>(); + + cb.put("style", sortFieldStyle); + cb.put("title", titleText); + cb.put("linkUrl", linkUrl); + cb.put("ajaxEnabled", ajaxEnabled); + String tooltip = modelFormField.getSortFieldHelpText(context); + if (!tooltip.isEmpty()) { + cb.put(" tooltip", tooltip); + } +// String target = modelForm.getPaginateTarget(); +// cb.put("paginateTarget", target); + List onPaginateUpdateAreas = modelForm.getOnPaginateUpdateAreas(); + if (!onPaginateUpdateAreas.isEmpty()) { + List> onPaginateUpdateAreasJson = new ArrayList<>(); + for (UpdateArea updateArea : onPaginateUpdateAreas) { + HashMap updateAreaJson = new HashMap<>(); + updateAreaJson.put("areaId", updateArea.getAreaId()); + updateAreaJson.put("areaTarget", updateArea.getAreaTarget()); + updateAreaJson.put("parameters", updateArea.getParameterMap(context)); + onPaginateUpdateAreasJson.add(updateAreaJson); + } + cb.put("onPaginateUpdateAreas", onPaginateUpdateAreasJson); + } else { + cb.put("onPaginateUpdateAreas", new ArrayList<>()); + } + cb.put("paginateTarget", modelForm.getPaginateTarget()); + String entityField = modelFormField.getParameterName(context); + cb.put("entityField", entityField); + this.output.putScreen("SortField", cb); + } + + /** Create an ajaxXxxx JavaScript CSV string from a list of UpdateArea objects. See + * OfbizUtil.js. + * @param updateAreas + * @param extraParams Renderer-supplied additional target parameters + * @param context + * @return Parameter string or empty string if no UpdateArea objects were found + */ + private String createAjaxParamsFromUpdateAreas(List updateAreas, Map extraParams, String anchor, Map context) { + StringBuilder sb = new StringBuilder(); + Iterator updateAreaIter = updateAreas.iterator(); + while (updateAreaIter.hasNext()) { + UpdateArea updateArea = updateAreaIter.next(); + sb.append(updateArea.getAreaId()).append(","); + String ajaxTarget = updateArea.getAreaTarget(context); + String urlPath = UtilHttp.removeQueryStringFromTarget(ajaxTarget); + sb.append(this.rh.makeLink(this.request, this.response,urlPath)).append(","); + String queryString = UtilHttp.getQueryStringFromTarget(ajaxTarget).replace("?", ""); + Map parameters = UtilHttp.getQueryStringOnlyParameterMap(queryString); + Map ctx = UtilGenerics.cast(context); + Map updateParams = UtilGenerics.cast(updateArea.getParameterMap(ctx)); + parameters.putAll(updateParams); + UtilHttp.canonicalizeParameterMap(parameters); + parameters.putAll(extraParams); + Iterator> paramIter = parameters.entrySet().iterator(); + while (paramIter.hasNext()) { + Map.Entry entry = paramIter.next(); + sb.append(entry.getKey()).append("=").append(entry.getValue()); + if (paramIter.hasNext()) { + sb.append("&"); + } + } + if (anchor != null) { + sb.append("#").append(anchor); + } + if (updateAreaIter.hasNext()) { + sb.append(","); + } + } + Locale locale = UtilMisc.ensureLocale(context.get("locale")); + return FlexibleStringExpander.expandString(sb.toString(), context, locale); + } + + public String createAjaxParamsFromUpdateAreas(List updateAreas, String extraParams, Map context) { + + if (updateAreas == null) { + return ""; + } + String ajaxUrl = ""; + boolean firstLoop = true; + for (UpdateArea updateArea : updateAreas) { + if (firstLoop) { + firstLoop = false; + } else { + ajaxUrl += ","; + } + Map ctx = UtilGenerics.cast(context); + Map parameters = updateArea.getParameterMap(ctx); + String targetUrl = updateArea.getAreaTarget(context); + String ajaxParams; + StringBuffer ajaxParamsBuffer = new StringBuffer(); + ajaxParamsBuffer.append(getAjaxParamsFromTarget(targetUrl)); + //add first parameters from updateArea parameters + if (UtilValidate.isNotEmpty(parameters)) { + for (Map.Entry entry : parameters.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + //test if ajax parameters are not already into extraParams, if so do not add it + if (UtilValidate.isNotEmpty(extraParams) && extraParams.contains(value)) { + continue; + } + if (ajaxParamsBuffer.length() > 0 && ajaxParamsBuffer.indexOf(key) < 0) { + ajaxParamsBuffer.append("&"); + } + if (ajaxParamsBuffer.indexOf(key) < 0) { + ajaxParamsBuffer.append(key).append("=").append(value); + } + } + } + //then add parameters from request. Those parameters could end with an anchor so we must set ajax parameters first + if (UtilValidate.isNotEmpty(extraParams)) { + if (ajaxParamsBuffer.length() > 0 && !extraParams.startsWith("&")) { + ajaxParamsBuffer.append("&"); + } + ajaxParamsBuffer.append(extraParams); + } + ajaxParams = ajaxParamsBuffer.toString(); + ajaxUrl += updateArea.getAreaId() + ","; + //ajaxUrl += this.rh.makeLink(this.request, this.response, UtilHttp.removeQueryStringFromTarget(targetUrl)); + ajaxUrl += "," + ajaxParams; + } + Locale locale = UtilMisc.ensureLocale(context.get("locale")); + return FlexibleStringExpander.expandString(ajaxUrl, context, locale); + } + + /** Extracts parameters from a target URL string, prepares them for an Ajax + * JavaScript call. This method is currently set to return a parameter string + * suitable for the Prototype.js library. + * @param target Target URL string + * @return Parameter string + */ + public static String getAjaxParamsFromTarget(String target) { + String targetParams = UtilHttp.getQueryStringFromTarget(target); + targetParams = targetParams.replace("?", ""); + targetParams = targetParams.replace("&", "&"); + return targetParams; + } + + /** + * If tooltip attribute exist add it with it's own attributes + * @param attributes + * @param context + * @param modelFormField + */ + public void appendTooltip(Map attributes, Map context, ModelFormField modelFormField) { + // render the tooltip, in other methods too + String tooltip = modelFormField.getTooltip(context); + if (UtilValidate.isNotEmpty(tooltip)) { + Map tooltipAttr = new HashMap<>(); + tooltipAttr.put("tooltip", encodeDoubleQuotes(tooltip)); + tooltipAttr.put("tooltipStyle", modelFormField.getTooltipStyle()); + attributes.put("tooltip", tooltipAttr); + } + } + + public void makeHyperlinkString(Map element, SubHyperlink subHyperlink, Map context) { + if (subHyperlink == null) { + return; + } + if (subHyperlink.shouldUse(context)) { + if (UtilValidate.isNotEmpty(subHyperlink.getWidth())) { + this.request.setAttribute("width", subHyperlink.getWidth()); + } + if (UtilValidate.isNotEmpty(subHyperlink.getHeight())) { + this.request.setAttribute("height", subHyperlink.getHeight()); + } + makeHyperlinkByType(element, subHyperlink.getLinkType(), subHyperlink.getStyle(context), subHyperlink.getUrlMode(), + subHyperlink.getTarget(context), subHyperlink.getParameterMap(context, subHyperlink.getModelFormField().getEntityName(), subHyperlink.getModelFormField().getServiceName()), subHyperlink.getDescription(context), + subHyperlink.getTargetWindow(context), "", subHyperlink.getModelFormField(), this.request, this.response, + context); + } + } + + public void addAsterisks(Map element, Map context, ModelFormField modelFormField) { + String requiredField = "false"; + String requiredStyle = ""; + if (modelFormField.getRequiredField()) { + requiredField = "true"; + requiredStyle = modelFormField.getRequiredFieldStyle(); + } + Map cb = new HashMap<>(); + cb.put("requiredField", requiredField); + cb.put("requiredStyle", requiredStyle); + element.put("required", cb); + } + + public String appendContentUrl(String location) { + StringBuilder buffer = new StringBuilder(); + ContentUrlTag.appendContentPrefix(this.request, buffer); + return buffer.toString() + location; + } + + public void makeHyperlinkByType(Map element, String linkType, String linkStyle, String targetType, String target, Map parameterMap, String description, String targetWindow, String confirmation, ModelFormField modelFormField, HttpServletRequest request, + HttpServletResponse response, Map context) { + element.put("HyperlinkByType", NOT_YET_SUPPORTED); + /* + String realLinkType = WidgetWorker.determineAutoLinkType(linkType, target, targetType, request); + String encodedDescription = encode(description, modelFormField, context); + // get the parameterized pagination index and size fields + int paginatorNumber = WidgetWorker.getPaginatorNumber(context); + ModelForm modelForm = modelFormField.getModelForm(); + ModelTheme modelTheme = visualTheme.getModelTheme(); + String viewIndexField = modelForm.getMultiPaginateIndexField(context); + String viewSizeField = modelForm.getMultiPaginateSizeField(context); + int viewIndex = Paginator.getViewIndex(modelForm, context); + int viewSize = Paginator.getViewSize(modelForm, context); + if (("viewIndex" + "_" + paginatorNumber).equals(viewIndexField)) { + viewIndexField = "VIEW_INDEX" + "_" + paginatorNumber; + } + if (("viewSize" + "_" + paginatorNumber).equals(viewSizeField)) { + viewSizeField = "VIEW_SIZE" + "_" + paginatorNumber; + } + if ("hidden-form".equals(realLinkType)) { + parameterMap.put(viewIndexField, Integer.toString(viewIndex)); + parameterMap.put(viewSizeField, Integer.toString(viewSize)); + if ("multi".equals(modelForm.getType())) { + WidgetWorker.makeHiddenFormLinkAnchor(writer, linkStyle, encodedDescription, confirmation, modelFormField, request, response, context); + // this is a bit trickier, since we can't do a nested form we'll have to put the link to submit the form in place, but put the actual form def elsewhere, ie after the big form is closed + Map wholeFormContext = UtilGenerics.checkMap(context.get("wholeFormContext")); + Appendable postMultiFormWriter = wholeFormContext != null ? (Appendable) wholeFormContext.get("postMultiFormWriter") : null; + if (postMultiFormWriter == null) { + postMultiFormWriter = new StringWriter(); + wholeFormContext.put("postMultiFormWriter", postMultiFormWriter); + } + WidgetWorker.makeHiddenFormLinkForm(postMultiFormWriter, target, targetType, targetWindow, parameterMap, modelFormField, request, response, context); + } else { + WidgetWorker.makeHiddenFormLinkForm(writer, target, targetType, targetWindow, parameterMap, modelFormField, request, response, context); + WidgetWorker.makeHiddenFormLinkAnchor(writer, linkStyle, encodedDescription, confirmation, modelFormField, request, response, context); + } + } else { + if ("layered-modal".equals(realLinkType)) { + String uniqueItemName = "Modal_".concat(UUID.randomUUID().toString().replace("-", "_")); + String width = (String) this.request.getAttribute("width"); + if (UtilValidate.isEmpty(width)) { + width = String.valueOf(modelTheme.getLinkDefaultLayeredModalWidth()); + this.request.setAttribute("width", width); + } + String height = (String) this.request.getAttribute("height"); + if (UtilValidate.isEmpty(height)) { + height = String.valueOf(modelTheme.getLinkDefaultLayeredModalHeight()); + this.request.setAttribute("height", height); + } + this.request.setAttribute("uniqueItemName", uniqueItemName); + makeHyperlinkString(cb, linkStyle, targetType, target, parameterMap, encodedDescription, confirmation, modelFormField, request, response, context, targetWindow); + this.request.removeAttribute("uniqueItemName"); + this.request.removeAttribute("height"); + this.request.removeAttribute("width"); + } else { + makeHyperlinkString(cb, linkStyle, targetType, target, parameterMap, encodedDescription, confirmation, modelFormField, request, response, context, targetWindow); + } + } + */ + } + + public void makeHyperlinkString(Map element, String linkStyle, String targetType, String target, Map parameterMap, String description, String confirmation, ModelFormField modelFormField, HttpServletRequest request, HttpServletResponse response, Map context, + String targetWindow) throws IOException { + if (description != null || UtilValidate.isNotEmpty(request.getAttribute("image"))) { + StringBuilder linkUrl = new StringBuilder(); + WidgetWorker.buildHyperlinkUrl(linkUrl, target, targetType, UtilValidate.isEmpty(request.getAttribute("uniqueItemName"))?parameterMap:null, null, false, false, true, request, response, context); + String event = ""; + String action = ""; + String imgSrc = ""; + String alt = ""; + String id = ""; + String uniqueItemName = ""; + String width = ""; + String height = ""; + String imgTitle = ""; + String hiddenFormName = WidgetWorker.makeLinkHiddenFormName(context, modelFormField); + if (UtilValidate.isNotEmpty(modelFormField.getEvent()) && UtilValidate.isNotEmpty(modelFormField.getAction(context))) { + event = modelFormField.getEvent(); + action = modelFormField.getAction(context); + } + if (UtilValidate.isNotEmpty(request.getAttribute("image"))) { + imgSrc = request.getAttribute("image").toString(); + } + if (UtilValidate.isNotEmpty(request.getAttribute("alternate"))) { + alt = request.getAttribute("alternate").toString(); + } + if (UtilValidate.isNotEmpty(request.getAttribute("imageTitle"))) { + imgTitle = request.getAttribute("imageTitle").toString(); + } + Integer size = Integer.valueOf("0"); + if (UtilValidate.isNotEmpty(request.getAttribute("descriptionSize"))) { + size = Integer.valueOf(request.getAttribute("descriptionSize").toString()); + } + if (UtilValidate.isNotEmpty(description) && size > 0 && description.length() > size) { + imgTitle = description; + description = description.substring(0, size - 8) + "..." + description.substring(description.length() - 5); + } + if (UtilValidate.isEmpty(imgTitle)) { + imgTitle = modelFormField.getTitle(context); + } + if (UtilValidate.isNotEmpty(request.getAttribute("id"))) { + id = request.getAttribute("id").toString(); + } + if (UtilValidate.isNotEmpty(request.getAttribute("uniqueItemName"))) { + uniqueItemName = request.getAttribute("uniqueItemName").toString(); + width = request.getAttribute("width").toString(); + height = request.getAttribute("height").toString(); + } + StringBuilder targetParameters = new StringBuilder(); + if (UtilValidate.isNotEmpty(parameterMap) ) { + targetParameters.append("{"); + for (Map.Entry parameter : parameterMap.entrySet()) { + if (targetParameters.length() > 1) { + targetParameters.append(","); + } + targetParameters.append("'"); + targetParameters.append(parameter.getKey()); + targetParameters.append("':'"); + targetParameters.append(parameter.getValue()); + targetParameters.append("'"); + } + targetParameters.append("}"); + } + Map cb = new HashMap<>(); + cb.put("linkStyle", linkStyle == null ? "" : linkStyle); + cb.put("hiddenFormName", hiddenFormName); + cb.put("event", event); + cb.put("action", action); + cb.put("imgSrc", imgSrc); + cb.put("title", imgTitle); + cb.put("alternate", alt); + cb.put("targetParameters", targetParameters.toString()); + cb.put("linkUrl", linkUrl.toString()); + cb.put("targetWindow", targetWindow); + cb.put("description", description); + cb.put("confirmation", confirmation); + cb.put("uniqueItemName", uniqueItemName); + cb.put("height", height); + cb.put("width", width); + cb.put("id", id); + element.put("HyperlinkString", cb); + } + } + + public void makeHiddenFormLinkAnchor(Appendable writer, String linkStyle, String description, String confirmation, ModelFormField modelFormField, HttpServletRequest request, HttpServletResponse response, Map context) { + if (UtilValidate.isNotEmpty(description) || UtilValidate.isNotEmpty(request.getAttribute("image"))) { + String hiddenFormName = WidgetWorker.makeLinkHiddenFormName(context, modelFormField); + String event = ""; + String action = ""; + String imgSrc = ""; + if (UtilValidate.isNotEmpty(modelFormField.getEvent()) && UtilValidate.isNotEmpty(modelFormField.getAction(context))) { + event = modelFormField.getEvent(); + action = modelFormField.getAction(context); + } + if (UtilValidate.isNotEmpty(request.getAttribute("image"))) { + imgSrc = request.getAttribute("image").toString(); + } + Map cb = new HashMap<>(); + + cb.put("linkStyle", linkStyle == null ? "" : linkStyle); + cb.put("hiddenFormName", hiddenFormName); + cb.put("event", event); + cb.put("action", action); + cb.put("imgSrc", imgSrc); + cb.put("description", description); + cb.put("confirmation ", confirmation); + this.output.putScreen("HiddenFormLinkAnchor", cb); + } + } +/* + public void makeHiddenFormLinkForm(Appendable writer, String target, String targetType, String targetWindow, List parameterList, ModelFormField modelFormField, HttpServletRequest request, HttpServletResponse response, Map context) throws IOException { + StringBuilder actionUrl = new StringBuilder(); + WidgetWorker.buildHyperlinkUrl(actionUrl, target, targetType, null, null, false, false, true, request, response, context); + String name = WidgetWorker.makeLinkHiddenFormName(context, modelFormField); + StringBuilder parameters = new StringBuilder(); + parameters.append("["); + for (CommonWidgetModels.Parameter parameter : parameterList) { + if (parameters.length() > 1) { + parameters.append(","); + } + parameters.append("{'name':'"); + parameters.append(parameter.getName()); + parameters.append("'"); + parameters.append(",'value':'"); + parameters.append(UtilCodec.getEncoder("html").encode(parameter.getValue(context))); + parameters.append("'}"); + } + parameters.append("]"); + Map cb = new HashMap<>(); + + cb.put("actionUrl", actionUrl.toString()); + cb.put("name", name); + cb.put("parameters", parameters.toString()); + cb.put(" targetWindow", targetWindow); + this.output.put("HiddenFormLinkForm", cb); + } +*/ + public void renderContainerFindField(Appendable writer, Map context, ContainerField containerField) { + String id = containerField.getModelFormField().getIdName(); + String className = UtilFormatOut.checkNull(containerField.getModelFormField().getWidgetStyle()); + Map cb = new HashMap<>(); + + cb.put("id", id); + cb.put("className", className); + this.output.putScreen("ContainerFindField", cb); + } + +} diff --git a/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsMenuRenderer.java b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsMenuRenderer.java new file mode 100644 index 000000000..b9280741b --- /dev/null +++ b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsMenuRenderer.java @@ -0,0 +1,279 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +package org.apache.ofbiz.widget.renderer.frontjs; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.ofbiz.base.util.StringUtil; +import org.apache.ofbiz.base.util.UtilCodec; +import org.apache.ofbiz.base.util.UtilHttp; +import org.apache.ofbiz.base.util.UtilMisc; +import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.webapp.control.RequestHandler; +import org.apache.ofbiz.webapp.taglib.ContentUrlTag; +import org.apache.ofbiz.widget.WidgetWorker; +import org.apache.ofbiz.widget.model.CommonWidgetModels.Image; +import org.apache.ofbiz.widget.model.ModelMenu; +import org.apache.ofbiz.widget.model.ModelMenuItem; +import org.apache.ofbiz.widget.model.ModelMenuItem.MenuLink; +import org.apache.ofbiz.widget.model.ModelTheme; +import org.apache.ofbiz.widget.renderer.MenuStringRenderer; +import org.apache.ofbiz.widget.renderer.VisualTheme; + +public class FrontJsMenuRenderer implements MenuStringRenderer { + + private static final String MODULE = FrontJsMenuRenderer.class.getName(); + private final HttpServletRequest request; + private final HttpServletResponse response; + private FrontJsOutput output; + + FrontJsMenuRenderer(FrontJsOutput output, HttpServletRequest request, HttpServletResponse response) { + this.output = output; + this.request = request; + this.response = response; + } + + // Made this a separate method so it can be externalized and reused. + private Map createImageParameters(Map context, Image image) { + Map parameters = new HashMap<>(); + parameters.put("id", image.getId(context)); + parameters.put("style", image.getStyle(context)); + parameters.put("width", image.getWidth(context)); + parameters.put("height", image.getHeight(context)); + parameters.put("border", image.getBorder(context)); + parameters.put("title", image.getTitleExdr().expandString(context)); + String src = image.getSrc(context); + if (UtilValidate.isNotEmpty(src) && request != null && response != null) { + String urlMode = image.getUrlMode(); + if ("ofbiz".equalsIgnoreCase(urlMode)) { + ServletContext ctx = (ServletContext) request.getAttribute("servletContext"); + RequestHandler rh = (RequestHandler) ctx.getAttribute("_REQUEST_HANDLER_"); + src = rh.makeLink(request, response, src, false, false, false); + } else if ("content".equalsIgnoreCase(urlMode)) { + StringBuilder newURL = new StringBuilder(); + ContentUrlTag.appendContentPrefix(request, newURL); + newURL.append(src); + src = newURL.toString(); + } + } + parameters.put("src", src); + return parameters; + } + + private boolean isDisableIfEmpty(ModelMenuItem menuItem, Map context) { + boolean disabled = false; + String disableIfEmpty = menuItem.getDisableIfEmpty(); + if (UtilValidate.isNotEmpty(disableIfEmpty)) { + List keys = StringUtil.split(disableIfEmpty, "|"); + for (String key : keys) { + Object obj = context.get(key); + if (UtilValidate.isEmpty(obj)) { + disabled = true; + break; + } + } + } + return disabled; + } + + private boolean isHideIfSelected(ModelMenuItem menuItem, Map context) { + ModelMenu menu = menuItem.getModelMenu(); + String currentMenuItemName = menu.getSelectedMenuItemContextFieldName(context); + String currentItemName = menuItem.getName(); + Boolean hideIfSelected = menuItem.getHideIfSelected(); + return (hideIfSelected != null && hideIfSelected && currentMenuItemName != null && currentMenuItemName.equals(currentItemName)); + } + + @Override + public void renderFormatSimpleWrapperClose(Appendable writer, Map context, ModelMenu menu) { + // Nothing to do. + } + + @Override + public void renderFormatSimpleWrapperOpen(Appendable writer, Map context, ModelMenu menu) { + // Nothing to do. + } + + @Override + public void renderFormatSimpleWrapperRows(Appendable writer, Map context, Object menu) throws IOException { + List menuItemList = ((ModelMenu) menu).getMenuItemList(); + for (ModelMenuItem currentMenuItem : menuItemList) { + renderMenuItem(writer, context, currentMenuItem); + } + } + + @Override + public void renderImage(Appendable writer, Map context, Image image) { + Map parameters = createImageParameters(context, image); + this.output.putScreen("Image", parameters); + } + + @Override + public void renderLink(Appendable writer, Map context, MenuLink link) throws IOException { + if (link.getLink().getRequestConfirmation()) { + throw new IOException("Render (Macro and FrontJs): requestConfirmation is used in a menuLink and it's not yet implemented" + + "for link with target="+ link.getTarget(context)); + } + + Map parameters = new HashMap<>(); + VisualTheme visualTheme = UtilHttp.getVisualTheme(request); + ModelTheme modelTheme = visualTheme.getModelTheme(); + String target = link.getTarget(context); + ModelMenuItem menuItem = link.getLinkMenuItem(); + if (isDisableIfEmpty(menuItem, context)) { + target = null; + } + if (UtilValidate.isNotEmpty(link.getId(context))) parameters.put("id", link.getId(context)); + if (UtilValidate.isNotEmpty(link.getStyle(context))) parameters.put("style", link.getStyle(context)); + if (UtilValidate.isNotEmpty(link.getName(context))) parameters.put("name", link.getName(context)); + if (UtilValidate.isNotEmpty(link.getText(context))) parameters.put("text", link.getText(context)); + String height = link.getHeight(); + if (UtilValidate.isEmpty(height)) { + height = String.valueOf(modelTheme.getLinkDefaultLayeredModalHeight()); + } + parameters.put("height", height); + String width = link.getWidth(); + if (UtilValidate.isEmpty(width)) { + width = String.valueOf(modelTheme.getLinkDefaultLayeredModalWidth()); + } + parameters.put("width", width); + // targetWindow is used for setArea, if link-type="anchor" + if (UtilValidate.isNotEmpty(link.getTargetWindow(context))) parameters.put("targetWindow", link.getTargetWindow(context)); + if (UtilValidate.isNotEmpty(link.getUrlMode())) parameters.put("urlMode", link.getUrlMode()); + // uniqueItemName is used for link-type='hidden-form' but this link-type is not currently supported by vuejs + StringBuilder uniqueItemName = new StringBuilder(menuItem.getModelMenu().getName()); + uniqueItemName.append("_").append(menuItem.getName()).append("_LF_").append(UtilMisc.addToBigDecimalInMap(context, "menuUniqueItemIndex", BigDecimal.ONE)); + if (menuItem.getModelMenu().getExtraIndex(context) != null) { + uniqueItemName.append("_").append(menuItem.getModelMenu().getExtraIndex(context)); + } + if (context.containsKey("itemIndex")) { + if (context.containsKey("parentItemIndex")) { + uniqueItemName.append(context.get("parentItemIndex")).append("_").append(context.get("itemIndex")); + } else { + uniqueItemName.append("_").append(context.get("itemIndex")); + } + } + parameters.put("uniqueItemName", uniqueItemName.toString()); + String linkType = ""; + if (UtilValidate.isNotEmpty(target)) { + linkType = WidgetWorker.determineAutoLinkType(link.getLinkType(), target, link.getUrlMode(), request); + } + // Workaround OH 2019-03-04 currently in VueLink hidden-form is not correctly manage, so use "auto" as link-type not hidden-form + // should be study when hidden-form will be manage + parameters.put("linkType", linkType); + parameters.put("linkType", link.getLinkType()); + // End of workaround + // linkUrl is no more sent but if link-type=inter-app it's needed to have String externalLoginKey = (String) request.getAttribute("externalLoginKey"); (cf WidgetWorker.buildHyperlinkUrl) + String linkUrl = ""; + if (UtilValidate.isNotEmpty(target)) { + if (!"hidden-form".equals(linkType)) { + StringBuilder sb = new StringBuilder(); + WidgetWorker.buildHyperlinkUrl(sb, target, link.getUrlMode(), "layered-modal".equals(linkType)?null:link.getParameterMap(context), link.getPrefix(context), + link.getFullPath(), link.getSecure(), link.getEncode(), request, response, context); + linkUrl = sb.toString(); + } + } + parameters.put("target", target); + parameters.put("linkUrl", linkUrl); + parameters.put("parameterMap", link.getParameterMap(context)); + Image img = link.getImage(); + if (img != null) { + parameters.put("img", createImageParameters(context, img)); + } + this.output.putScreen("Link", parameters); + } + + @Override + public void renderMenuOpen(Appendable writer, Map context, ModelMenu menu) { + Map parameters = new HashMap<>(); + parameters.put("id", menu.getId()); + parameters.put("style", menu.getMenuContainerStyle(context)); + parameters.put("title", menu.getTitle(context)); + this.output.pushScreen("MenuBegin", parameters); + } + + @Override + public void renderMenuItem(Appendable writer, Map context, ModelMenuItem menuItem) throws IOException { + if (isHideIfSelected(menuItem, context)) { + return; + } + Map parameters = new HashMap<>(); + String style = menuItem.getWidgetStyle(); + if (menuItem.isSelected(context)) { + String selectedStyle = menuItem.getSelectedStyle(); + if (UtilValidate.isEmpty(selectedStyle)) { + selectedStyle = "selected"; + } + if (UtilValidate.isNotEmpty(style)) { + style += " " ; + } + style += selectedStyle ; + } + if (this.isDisableIfEmpty(menuItem, context)) { + style = menuItem.getDisabledTitleStyle(); + } + if (style == null) { + style = ""; + } + String alignStyle = menuItem.getAlignStyle(); + if (UtilValidate.isNotEmpty(alignStyle)) { + style = style.concat(" ").concat(alignStyle); + } + parameters.put("style", style); + parameters.put("toolTip", menuItem.getTooltip(context)); + boolean containsNestedMenus = !menuItem.getMenuItemList().isEmpty(); + parameters.put("containsNestedMenus", containsNestedMenus); + this.output.pushScreen("MenuItemBegin", parameters); + String linkStr; + MenuLink link = menuItem.getLink(); + if (link != null) { + renderLink(writer, context, link); + } else { + linkStr = menuItem.getTitle(context); + UtilCodec.SimpleEncoder simpleEncoder = (UtilCodec.SimpleEncoder) context.get("simpleEncoder"); + if (simpleEncoder != null) { + linkStr = simpleEncoder.encode(linkStr); + } + parameters.put("Link", linkStr); + } + + if (containsNestedMenus) { + for (ModelMenuItem childMenuItem : menuItem.getMenuItemList()) { + renderMenuItem(writer, context, childMenuItem); + } + } + parameters.clear(); + parameters.put("containsNestedMenus", containsNestedMenus); + this.output.popScreen("MenuItemEnd"); + } + + @Override + public void renderMenuClose(Appendable writer, Map context, ModelMenu menu) { + this.output.popScreen("MenuEnd"); + } +} diff --git a/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsOutput.java b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsOutput.java new file mode 100644 index 000000000..552caa48b --- /dev/null +++ b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsOutput.java @@ -0,0 +1,268 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ +package org.apache.ofbiz.widget.renderer.frontjs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import org.apache.ofbiz.base.util.UtilGenerics; +import org.apache.ofbiz.base.util.UtilMisc; + +public class FrontJsOutput { + private static final String MODULE = FrontJsOutput.class.getName(); + + + Map output = new HashMap<>(); + private List> viewScreen; + private Map viewEntities = new HashMap<>(); + + // stack of screen, a screen is a list of element (see list of renderer method (screen, form, menu,..), each one is a element) + // each element has 3 fields + // - "name" the element name (ex: Label, ScreenletBegin, DisplayField, HyperlinkField, ...). To have the complete list of screen element search all call to "output.*Screen(" + // - "attributes" a map with the attributes + // - "stPointer" only there when it's a field with a link to the store in the FrontJs application, it's a map which data to be able to create the link + // - and a fourth for temporary Debug purpose "dataDebug" + private Stack>> screensStack; + + // stack of entity which is currently used for store records + // contain 3 field + // - "primaryKeys" list of fieldName which are pk in entityName. Used to build pkValue for storePointer + // - "list" list of records (each record contain its stId) + // - "entityName" + private Stack> entitiesStack; + + // use to build record, a map with content of fields (fieldName : value) for a row or a single form + // similar as a GenericValue but with fields define in the form + // it's a stack because in a field I can have a sub-list about an other entities + private Stack> recordsStack; + + FrontJsOutput(String name) { + viewScreen = new ArrayList<>(); + output.put("viewScreenName", name); + output.put("viewScreen", viewScreen); + output.put("viewEntities", viewEntities); + screensStack = new Stack<>(); + entitiesStack = new Stack<>(); + recordsStack = new Stack<>(); + screensStack.push(viewScreen); + } + + + /** + * Add a new screenElement into the children of the top screen of the stack.
+ * Should be used when element is not a field otherwise used {@link FrontJsOutput#putScreen(String, Map, String, String)}. + * + * @param name : the element Name (see list of renderer method (screen, form, menu,..), each one is a element) + * @param attributes : a map with all the attributes elements + */ + void putScreen(String name, Map attributes) { + putScreen(name, attributes, null, null); + } + + /** + * Add a new screenElement into the children of the top screen of the stack.
+ * Should be used with fieldName!=null only when element is a field.
+ * Add (fieldName : value) in the recordStack. + * + * @param name : the element Name (see list of renderer method (screen, form, menu,..), each one is a element) + * @param attributes : a map with all the attributes elements + * @param fieldName : if element is a field, its name otherwise should be null + * @param fieldValue : if element is a field, its value otherwise should be null + */ + void putScreen(String name, Map attributes, String fieldName, String fieldValue) { + Map screen = new HashMap<>(); + screen.put("attributes", attributes); + screen.put("name", name); + screensStack.peek().add(screen); + if (fieldName != null && !recordsStack.empty()) { + Map stPointer = new HashMap<>(); + stPointer.put("stEntityName", (String) this.entitiesStack.peek().get("entityName")); + stPointer.put("id", (String) recordsStack.peek().get("stId")); + stPointer.put("field", fieldName); + screen.put("stPointer", stPointer); + this.putRecord(fieldName, fieldValue); + // for debug purpose only, should be remove when the old code with PUT_RECORD will be removed + screen.put("dataDebug", UtilMisc.toMap("action", "PUT_RECORD", "key", fieldName, "value", fieldValue)); + } + } + + /** + * Read and return the first screenElement from screensStack.peek and remove it from screensStack.peek. + *
It's used for screenlet to be able to put tabMenu or navMenu as attribute and not as children. + * For this use case there will be only 1 screen in list when this method will be call. + *
If new use case appear, this method will work with last screen of the list + * @return the first screenElement from screensStack.peek and remove it from screensStack.peek + */ + Map getAndRemoveScreen() { + Map screen = screensStack.peek().get(0); + screensStack.peek().remove(0); + return screen; + } + + /** + * Push a new screenElement into the children of the top screen of the stack.
+ * Should be used only when element is not a field. + * + * @param name : the element Name (see list of renderer method (screen, form, menu,..), each one is a element) + * @param attributes : a map with all the attributes elements + */ + void pushScreen(String name, Map attributes) { + pushScreen(name, attributes, null, null); + } + + /** + * Push a new screenElement into the children of the top screen of the stack.
+ * Should be used only when element is not a field. + * + * @param name : the element Name (see list of renderer method (screen, form, menu,..), each one is a element) + * @param attributes : a map with all the attributes elements + * @param action : a string determining action to perform on data (entityStack) can be one of [null, "PUSH_ENTITY", "NEW_RECORD"] + * @param context : a map with the data relative to the action to perform + */ + void pushScreen(String name, Map attributes, String action, Map context) { + Map screen = new HashMap<>(); + // there is no Open/Begin or Close screenElement only screenElement with (or without) children + screen.put("name", name.replace("Open", "").replace("Begin", "")); + screen.put("attributes", attributes); + + List> children = new ArrayList>(); + screen.put("children", children); + screensStack.peek().add(screen); + screensStack.push(children); + if (action != null) { + if (action.equals("PUSH_ENTITY")) { + // TODO + this.pushEntity((String) context.get("entityName"), UtilGenerics.cast(context.get("primaryKeys"))); + } + if (action.equals("NEW_RECORD")) { + this.newRecord(context); + } + } + } + + /** + * Pop the top screenElement the current screen stack.
+ * Should be used only when element is not a field. + * + * @param name : the element Name (see list of renderer method (screen, form, menu,..), each one is a element) + */ + void popScreen(String name) {popScreen(name, null);} + /** + * Pop the top screenElement the current screen stack and perform some data actions.
+ * Should be used only when element is not a field. + * + * @param name : the element Name (see list of renderer method (screen, form, menu,..), each one is a element) + * @param action : a string with the action to perform on data (entityStack) can be one of [null, "POP_ENTITY", "STORE_RECORD"] + */ + void popScreen(String name, String action) { + screensStack.pop(); + if (action != null) { + if (action.equals("STORE_RECORD")) { + this.storeRecord(); + } + if (action.equals("POP_ENTITY")) { + this.popEntity(); + } + } + } + + /** + * Push entity on the stack (all record will be relative to this entity until another one was push or this one was pop).
+ * Should be called by pushScreen only. + * + * @param entityName : the name of the entity to push + * @param primaryKeys : the list of entity's primary key + */ + private void pushEntity(String entityName, List primaryKeys) { + Map entity; + if (!viewEntities.containsKey(entityName)) { + entity = new HashMap<>(); + entity.put("primaryKeys", primaryKeys); + entity.put("list", new ArrayList>()); + entity.put("entityName", entityName); + viewEntities.put(entityName, entity); + } else { + entity = UtilGenerics.cast(viewEntities.get(entityName)); + } + entitiesStack.push(entity); + } + + private void popEntity() { + entitiesStack.pop(); + } + + /** + * Push a new record on the stack and generate the primary key to use in front.
+ * Should be called by pushScreen only. + * + * @param context : the map of the current record (only used to get the primary key, it cant't be null) + */ + private void newRecord(Map context) { + if (!entitiesStack.empty()) { + // currentRecord + Map record = new HashMap<>(); + // build stPointerId + List pkList = UtilGenerics.cast(this.entitiesStack.peek().get("primaryKeys")); + int i = 0; + String pkey = ""; + do { + pkey += context.get(pkList.get(i)); + i++; + } while (i < pkList.size()); + record.put("stId", pkey); + recordsStack.push(record); + + List> entitiesStackPeekList = UtilGenerics.cast(entitiesStack.peek().get("list")); + entitiesStackPeekList.add(record); + } + } + + private void storeRecord() { + if (!recordsStack.empty()) { + recordsStack.pop(); + } + } + + private void putRecord(String fieldName, String value) { + if (!recordsStack.empty()) { + recordsStack.peek().put(fieldName, value); + } + } + + public Map output() { + return this.output; + } + + // TODO check if this method is used, seem not because "recordPointer" is not used in *.vue files + public Map getRecordPointer(Map context) { + if (!this.entitiesStack.empty()) { + Map data = new HashMap<>(); + String entityName = (String) this.entitiesStack.peek().get("entityName"); + data.put("entity", entityName); + List primaryKeys = UtilGenerics.cast(this.entitiesStack.peek().get("primaryKeys")); + data.put("id", context.get(primaryKeys.get(0))); + return data; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsScreenRenderer.java b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsScreenRenderer.java new file mode 100644 index 000000000..ca260ea95 --- /dev/null +++ b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsScreenRenderer.java @@ -0,0 +1,722 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ +package org.apache.ofbiz.widget.renderer.frontjs; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.ofbiz.base.util.Debug; +import org.apache.ofbiz.base.util.GeneralException; +import org.apache.ofbiz.base.util.StringUtil; +import org.apache.ofbiz.base.util.UtilGenerics; +import org.apache.ofbiz.base.util.UtilHttp; +import org.apache.ofbiz.base.util.UtilMisc; +import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.entity.GenericValue; +import org.apache.ofbiz.webapp.control.RequestHandler; +import org.apache.ofbiz.webapp.taglib.ContentUrlTag; +import org.apache.ofbiz.widget.WidgetWorker; +import org.apache.ofbiz.widget.model.AbstractModelAction; +import org.apache.ofbiz.widget.model.ModelForm; +import org.apache.ofbiz.widget.model.ModelMenu; +import org.apache.ofbiz.widget.model.ModelMenuItem; +import org.apache.ofbiz.widget.model.ModelScreen; +import org.apache.ofbiz.widget.model.ModelScreenWidget; +import org.apache.ofbiz.widget.model.ModelScreenWidget.ColumnContainer; +import org.apache.ofbiz.widget.model.ModelScreenWidget.ScreenImage; +import org.apache.ofbiz.widget.model.ModelTheme; +import org.apache.ofbiz.widget.model.ScreenFactory; +import org.apache.ofbiz.widget.renderer.MenuStringRenderer; +import org.apache.ofbiz.widget.renderer.Paginator; +import org.apache.ofbiz.widget.renderer.ScreenStringRenderer; +import org.apache.ofbiz.widget.renderer.VisualTheme; +import org.xml.sax.SAXException; + +public class FrontJsScreenRenderer implements ScreenStringRenderer { + private static final String MODULE = FrontJsScreenRenderer.class.getName(); + private FrontJsOutput output; + private String rendererName; + private int screenLetsIdCounter = 1; // not really usable because most of time FrontJsRenderer is call for just a screenlet, + // not for a screen with multiple screenlet included + + FrontJsScreenRenderer(String name, FrontJsOutput output) { + this.output = output; + rendererName = name; + } + public String getRendererName() { + return rendererName; + } + + public void renderScreenBegin(Appendable writer, Map context) throws IOException { + // nothing to do, it's only a human logic readable element + } + + public void renderScreenEnd(Appendable writer, Map context) throws IOException { + // nothing to do, it's only a human logic readable element + } + + public void renderSectionBegin(Appendable writer, Map context, ModelScreenWidget.Section section) throws IOException { + // nothing to do, it's only a human logic readable element + } + public void renderSectionEnd(Appendable writer, Map context, ModelScreenWidget.Section section) throws IOException { + // nothing to do, it's only a human logic readable element + } + + public void renderContainerBegin(Appendable writer, Map context, ModelScreenWidget.Container container) throws IOException { + String containerId = container.getId(context); + String type = container.getType(context); + if (UtilValidate.isNotEmpty(type)) { + throw new IOException("FrontJsRender: type property in container tag, not yet implemented in container" + + ((containerId!=null)? "with id="+containerId : "")); + } + Map parameters = new HashMap<>(); + parameters.put("id", containerId); // used as areaId + String autoUpdateTarget = container.getAutoUpdateTargetExdr(context); + if (UtilValidate.isNotEmpty(autoUpdateTarget)) { + parameters.put("autoUpdateTarget", autoUpdateTarget); + } + String watcherName = container.getWatcherNameExdr(context); + if (UtilValidate.isNotEmpty(watcherName)) { + List watcherList = StringUtil.split(watcherName,","); + if (watcherList.size()>1) { + StringBuilder watcherNameStr = new StringBuilder(); + watcherNameStr.append(watcherList.get(0).trim()); + for (int i = 1; i < watcherList.size(); i++) { + watcherNameStr.append("-"); + watcherNameStr.append(watcherList.get(i).trim()); + } + parameters.put("watcherName", watcherNameStr.toString()); + } else { + parameters.put("watcherName", watcherName); + } + } + if (! "2".equals(container.getAutoUpdateInterval(context))) { // 2 is the default value, if empty + throw new IOException("FrontJsRender: auto-update-interval property in container tag, not yet implemented in container" + + ((containerId!=null)? "with id="+containerId : "")); + } + if (UtilValidate.isNotEmpty(container.getStyle(context))) { + Debug.logWarning("style property is used (="+container.getStyle(context)+ + ") in container with id="+containerId+" it's not manage by FrontFjRenderer", MODULE); + parameters.put("style", container.getStyle(context)); + } + this.output.pushScreen("ContainerOpen", parameters); + } + + public void renderContainerEnd(Appendable writer, Map context, ModelScreenWidget.Container container) throws IOException { + this.output.popScreen("ContainerClose"); + } + + public void renderLabel(Appendable writer, Map context, ModelScreenWidget.Label label) throws IOException { + Map attributes = new HashMap<>(); + attributes.put("text", label.getText(context)); + if (UtilValidate.isNotEmpty(label.getId(context))) attributes.put("id", label.getId(context)); + if (UtilValidate.isNotEmpty(label.getStyle(context))) attributes.put("style", label.getStyle(context)); + this.output.putScreen("Label", attributes); + } + + public void renderVueJs(Appendable writer, Map context, ModelScreenWidget.VueJs vuejs) throws IOException { + Map attributes = vuejs.getParameterMap(context); + attributes.put("componentName", vuejs.getComponentName(context)); + this.output.putScreen("VueJs", attributes); + } + + public void renderHorizontalSeparator(Appendable writer, Map context, ModelScreenWidget.HorizontalSeparator separator) throws IOException { + Map attributes = new HashMap<>(); + if (UtilValidate.isNotEmpty(separator.getId(context))) { + Debug.logWarning("separator id is used (="+separator.getId(context)+ + ") it's not manage by FrontFjRenderer", MODULE); + attributes.put("id", separator.getId(context)); + } + if (UtilValidate.isNotEmpty(separator.getName())) { + Debug.logWarning("separator name is used (="+separator.getName()+ + ") it's not manage by FrontFjRenderer", MODULE); + attributes.put("name", separator.getName()); + } + if (UtilValidate.isNotEmpty(separator.getStyle(context))) { + Debug.logWarning("separator style is used (="+separator.getStyle(context)+ + ") it's not manage by FrontFjRenderer", MODULE); + attributes.put("style", separator.getStyle(context)); + } + this.output.putScreen("HorizontalSeparator", attributes); + } + + // not yet tested, it's very, very similar to MenuRenderer.renderLink which is tested + public void renderLink(Appendable writer, Map context, ModelScreenWidget.ScreenLink link) throws IOException { + HttpServletRequest request = (HttpServletRequest) context.get("request"); + VisualTheme visualTheme = UtilHttp.getVisualTheme(request); + ModelTheme modelTheme = visualTheme.getModelTheme(); + + if (link.getLink().getRequestConfirmation()) { + throw new IOException("Render (Macro and FrontJs): requestConfirmation is used in a screenLink and it's not yet implemented" + + "for link with target="+ link.getTarget(context)); + } + + Map parameters = new HashMap<>(); + String target = link.getTarget(context); + if (UtilValidate.isNotEmpty(link.getId(context))) parameters.put("id", link.getId(context)); + if (UtilValidate.isNotEmpty(link.getStyle(context))) parameters.put("style", link.getStyle(context)); + if (UtilValidate.isNotEmpty(link.getName(context))) parameters.put("name", link.getName(context)); + if (UtilValidate.isNotEmpty(link.getText(context))) parameters.put("text", link.getText(context)); + String height = link.getHeight(); + if (UtilValidate.isEmpty(height)) { + height = String.valueOf(modelTheme.getLinkDefaultLayeredModalHeight()); + } + parameters.put("height", height); + String width = link.getWidth(); + if (UtilValidate.isEmpty(width)) { + width = String.valueOf(modelTheme.getLinkDefaultLayeredModalWidth()); + } + parameters.put("width", width); + // targetWindow is used for setArea, if link-type="anchor" + if (UtilValidate.isNotEmpty(link.getTargetWindow(context))) parameters.put("targetWindow", link.getTargetWindow(context)); + if (UtilValidate.isNotEmpty(link.getUrlMode())) parameters.put("urlMode", link.getUrlMode()); + + // uniqueItemName is used for link-type='hidden-form' but this link-type is not currently supported by vuejs + String uniqueItemName = link.getModelScreen().getName() + "_LF_" + UtilMisc.addToBigDecimalInMap(context, "screenUniqueItemIndex", BigDecimal.ONE); + parameters.put("uniqueItemName", uniqueItemName); + String linkType = ""; + if (UtilValidate.isNotEmpty(target)) { + linkType = WidgetWorker.determineAutoLinkType(link.getLinkType(), target, link.getUrlMode(), request); + } + // Workaround OH 2019-03-04 currently in VueLink hidden-form is not correctly manage, so use "auto" as link-type not hidden-form + // should be study when hidden-form will be manage + parameters.put("linkType", linkType); + parameters.put("linkType", link.getLinkType()); + // End of workaround + // linkUrl is no more sent but if link-type=inter-app it's needed to have String externalLoginKey = (String) request.getAttribute("externalLoginKey"); (cf WidgetWorker.buildHyperlinkUrl) + parameters.put("target", target); + parameters.put("parameterMap", link.getParameterMap(context)); + ScreenImage img = link.getImage(); + if (img != null) { + parameters.put("img", createImageParameters(context, img)); + } + this.output.putScreen("Link", parameters); + } + // Made this a separate method so it can be externalized and reused. + // used by renderLink method + // copy from MenuRenderer + private Map createImageParameters(Map context, ScreenImage image) { + HttpServletResponse response = (HttpServletResponse) context.get("response"); + HttpServletRequest request = (HttpServletRequest) context.get("request"); + Map parameters = new HashMap<>(); + parameters.put("id", image.getId(context)); + parameters.put("style", image.getStyle(context)); + parameters.put("width", image.getWidth(context)); + parameters.put("height", image.getHeight(context)); + parameters.put("border", image.getBorder(context)); + // title attribute not exist for image in link in screen but exist in menu + //parameters.put("title", image.getTitleExdr().expandString(context)); + String src = image.getSrc(context); + if (UtilValidate.isNotEmpty(src) && request != null && response != null) { + String urlMode = image.getUrlMode(); + if ("ofbiz".equalsIgnoreCase(urlMode)) { + ServletContext ctx = (ServletContext) request.getAttribute("servletContext"); + RequestHandler rh = (RequestHandler) ctx.getAttribute("_REQUEST_HANDLER_"); + src = rh.makeLink(request, response, src, false, false, false); + } else if ("content".equalsIgnoreCase(urlMode)) { + StringBuilder newURL = new StringBuilder(); + ContentUrlTag.appendContentPrefix(request, newURL); + newURL.append(src); + src = newURL.toString(); + } + } + parameters.put("src", src); + return parameters; + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + // currently Images is associated to vue-error to generate the Warning message in screen. + public void renderImage(Appendable writer, Map context, ModelScreenWidget.ScreenImage image) throws IOException { + if (image == null) { + return ; + } + String src = image.getSrc(context); + + String urlMode = image.getUrlMode(); + boolean fullPath = false; + boolean secure = false; + boolean encode = false; + HttpServletResponse response = (HttpServletResponse) context.get("response"); + HttpServletRequest request = (HttpServletRequest) context.get("request"); + String urlString = ""; + if (urlMode != null && "intra-app".equalsIgnoreCase(urlMode)) { + if (request != null && response != null) { + ServletContext ctx = (ServletContext) request.getAttribute("servletContext"); + RequestHandler rh = (RequestHandler) ctx.getAttribute("_REQUEST_HANDLER_"); + urlString = rh.makeLink(request, response, src, fullPath, secure, encode); + } else { + urlString = src; + } + } else if (urlMode != null && "content".equalsIgnoreCase(urlMode)) { + if (request != null && response != null) { + StringBuilder newURL = new StringBuilder(); + ContentUrlTag.appendContentPrefix(request, newURL); + newURL.append(src); + urlString = newURL.toString(); + } + } else { + urlString = src; + } + Map parameters = new HashMap<>(); + parameters.put("src", src); + parameters.put("id", image.getId(context)); + parameters.put("style", image.getStyle(context)); + parameters.put("wid", image.getWidth(context)); + parameters.put("hgt", image.getHeight(context)); + parameters.put("border", image.getBorder(context)); + parameters.put("alt", image.getAlt(context)); + parameters.put("urlString", urlString); + this.output.putScreen("Image", parameters); + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + // currently ContentBegin is associated to vue-error to generate the Warning message in screen. + public void renderContentBegin(Appendable writer, Map context, ModelScreenWidget.Content content) throws IOException { + String editRequest = content.getEditRequest(context); + String enableEditName = content.getEnableEditName(context); + String enableEditValue = (String)context.get(enableEditName); + + if (Debug.verboseOn()) { + Debug.logVerbose("directEditRequest:" + editRequest, MODULE); + } + + Map parameters = new HashMap<>(); + parameters.put("editRequest", editRequest); + parameters.put("enableEditValue", enableEditValue == null ? "" : enableEditValue); + parameters.put("editContainerStyle", content.getEditContainerStyle(context)); + this.output.putScreen("ContentBegin", parameters); + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + public void renderContentBody(Appendable writer, Map context, ModelScreenWidget.Content content) throws IOException { + /* when it will be used, code should be copied from MacroScreenRenderer and at minimal + + writer.append(renderedContent); + should be replace by + Map cb = new HashMap<>(); + cb.put("content", renderedContent); + this.output.add("ContentBody", cb); + */ + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + public void renderContentEnd(Appendable writer, Map context, ModelScreenWidget.Content content) throws IOException { + // when it will be used, code should be copied from MacroScreenRenderer and adapted + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + public void renderContentFrame(Appendable writer, Map context, ModelScreenWidget.Content content) throws IOException { + // when it will be used, code should be copied from MacroScreenRenderer and adapted + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + public void renderSubContentBegin(Appendable writer, Map context, ModelScreenWidget.SubContent content) throws IOException { + // when it will be used, code should be copied from MacroScreenRenderer and adapted + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + public void renderSubContentBody(Appendable writer, Map context, ModelScreenWidget.SubContent content) throws IOException { + /* when it will be used, code should be copied from MacroScreenRenderer and at minimal + + writer.append(renderedContent); + should be replace by + Map cb = new HashMap<>(); + cb.put("content", renderedContent); + this.output.add("ContentBody", cb); +*/ + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + public void renderSubContentEnd(Appendable writer, Map context, ModelScreenWidget.SubContent content) throws IOException { + // when it will be used, code should be copied from MacroScreenRenderer and adapted + } + + + public void renderScreenletBegin(Appendable writer, Map context, boolean collapsed, ModelScreenWidget.Screenlet screenlet) throws IOException { + /* currently theme is not manage + HttpServletRequest request = (HttpServletRequest) context.get("request"); + HttpServletResponse response = (HttpServletResponse) context.get("response"); + VisualTheme visualTheme = UtilHttp.getVisualTheme(request); + ModelTheme modelTheme = visualTheme.getModelTheme(); + */ + + String title = screenlet.getTitle(context); + boolean collapsible = screenlet.collapsible(); + ModelScreenWidget.Menu tabMenu = screenlet.getTabMenu(); + ModelScreenWidget.Menu navMenu = screenlet.getNavigationMenu(); + ModelScreenWidget.Form navForm = screenlet.getNavigationForm(); + + Map parameters = new HashMap<>(); + parameters.put("title", title); + parameters.put("name", screenlet.getName()); + parameters.put("collapsible", collapsible); + if (screenlet.saveCollapsed()) parameters.put("saveCollapsed", true); + if (UtilValidate.isNotEmpty (screenlet.getId(context))) { + parameters.put("id", screenlet.getId(context)); + parameters.put("collapsibleAreaId", screenlet.getId(context) + "_col"); + } else { + if (collapsible) Debug.logWarning("Screenlet collapsible without an id", MODULE); + parameters.put("id", "screenlet_" + screenLetsIdCounter); + parameters.put("collapsibleAreaId","screenlet_" + screenLetsIdCounter + "_col"); + screenLetsIdCounter++; + } + if (! screenlet.padded()) { // default value is true, equal false only if attribute is present with false or naviguation-form is used + Debug.logWarning("screenlet attribute padded is used in screenlet with title="+title+ + " it's not manage by FrontFjRenderer", MODULE); + parameters.put("padded", screenlet.padded()); + } + parameters.put("collapsed", collapsed); + parameters.put("showMore", (Boolean) (UtilValidate.isNotEmpty(title) || navMenu != null || navForm != null || collapsible)); + this.output.pushScreen("ScreenletBegin", parameters); + + if (tabMenu != null) { + // generate menu object, to be able to put it in parameters rather than as children + // it's more easy for a frontJs component to manage attributes than sub-components + parameters.put("tabMenu", getMenuOutput(writer, context, tabMenu.getModelMenu(context))); + + } + if (navMenu != null || navForm != null ) { + if (navMenu != null) { + // generate menu object, to be able to put it in parameters rather than as children + // it's more easy for a frontJs component to manage attributes than sub-components + parameters.put("navMenu", getMenuOutput(writer, context, navMenu.getModelMenu(context))); + } else if (navForm != null) { + Debug.logWarning("navigation-form is used in screenlet with title="+title+ + " it's not manage by VueJs screenlet component", MODULE); + parameters.put("navForm",renderScreenletPaginateMenu(writer, context, navForm)); + } + } + } + // used by the method just above + private Map getMenuOutput(Appendable writer, Map context, ModelMenu menu) throws IOException{ + MenuStringRenderer menuStringRenderer = (MenuStringRenderer)context.get("menuStringRenderer"); + AbstractModelAction.runSubActions(menu.getActions(), context); + menuStringRenderer.renderMenuOpen(writer, context, menu); + + menuStringRenderer.renderFormatSimpleWrapperOpen(writer, context, menu); + for (ModelMenuItem item : menu.getMenuItemList()) { + if (item.shouldBeRendered(context)) { + AbstractModelAction.runSubActions(item.getActions(), context); + menuStringRenderer.renderMenuItem(writer, context, item); + } + } + menuStringRenderer.renderFormatSimpleWrapperClose(writer, context, menu); + menuStringRenderer.renderMenuClose(writer, context, menu); + return this.output.getAndRemoveScreen(); + } + + public void renderScreenletSubWidget(Appendable writer, Map context, ModelScreenWidget subWidget, ModelScreenWidget.Screenlet screenlet) throws GeneralException, IOException { + subWidget.renderWidgetString(writer, context, this); + // currently NavigationForm included in screenlet bar is not managed, maybe in future it will be necessary to add something like + // if (subWidget.equals(screenlet.getNavigationForm())) { ... (see field renderPagination in FrontJsFormRenderer) + } + public void renderScreenletEnd(Appendable writer, Map context, ModelScreenWidget.Screenlet screenlet) throws IOException { + this.output.popScreen("ScreenletEnd"); + } + + // not yet used, (no use case in screens.xml using FrontJsRenderer) + // need to be review before using, use method renderNextPrev from FrontJsFormRenderer to know which attribute is needed (renderNextPrev is used) + protected Map renderScreenletPaginateMenu(Appendable writer, Map context, ModelScreenWidget.Form form) throws IOException { + HttpServletResponse response = (HttpServletResponse) context.get("response"); + HttpServletRequest request = (HttpServletRequest) context.get("request"); + ModelForm modelForm; + try { + modelForm = form.getModelForm(context); + } catch (Exception e) { + throw new IOException(e); + } + modelForm.runFormActions(context); + Paginator.preparePager(modelForm, context); + String targetService = modelForm.getPaginateTarget(context); + if (targetService == null) { + targetService = "${targetService}"; + } + + // get the parametrized pagination index and size fields + int paginatorNumber = WidgetWorker.getPaginatorNumber(context); + String viewIndexParam = modelForm.getMultiPaginateIndexField(context); + String viewSizeParam = modelForm.getMultiPaginateSizeField(context); + + int viewIndex = Paginator.getViewIndex(modelForm, context); + int viewSize = Paginator.getViewSize(modelForm, context); + int listSize = Paginator.getListSize(context); + + int highIndex = Paginator.getHighIndex(context); + int actualPageSize = Paginator.getActualPageSize(context); + + // if this is all there seems to be (if listSize < 0, then size is unknown) + if (actualPageSize >= listSize && listSize >= 0) { + return null; + } + + // for legacy support, the viewSizeParam is VIEW_SIZE and viewIndexParam is VIEW_INDEX when the fields are "viewSize" and "viewIndex" + if (("viewIndex" + "_" + paginatorNumber).equals(viewIndexParam)) { + viewIndexParam = "VIEW_INDEX" + "_" + paginatorNumber; + } + if (("viewSize" + "_" + paginatorNumber).equals(viewSizeParam)) { + viewSizeParam = "VIEW_SIZE" + "_" + paginatorNumber; + } + + ServletContext ctx = (ServletContext) request.getAttribute("servletContext"); + RequestHandler rh = (RequestHandler) ctx.getAttribute("_REQUEST_HANDLER_"); + + Map inputFields = UtilGenerics.cast(context.get("requestParameters")); + // strip out any multi form fields if the form is of type multi + if ("multi".equals(modelForm.getType())) { + inputFields = UtilHttp.removeMultiFormParameters(inputFields); + } + String queryString = UtilHttp.urlEncodeArgs(inputFields); + // strip legacy viewIndex/viewSize params from the query string + queryString = UtilHttp.stripViewParamsFromQueryString(queryString, "" + paginatorNumber); + // strip parametrized index/size params from the query string + HashSet paramNames = new HashSet<>(); + paramNames.add(viewIndexParam); + paramNames.add(viewSizeParam); + queryString = UtilHttp.stripNamedParamsFromQueryString(queryString, paramNames); + + String anchor = ""; + String paginateAnchor = modelForm.getPaginateTargetAnchor(); + if (paginateAnchor != null) { + anchor = "#" + paginateAnchor; + } + + // preparing the link text, so that later in the code we can reuse this and just add the viewIndex + String prepLinkText = ""; + prepLinkText = targetService; + if (prepLinkText.indexOf('?') < 0) { + prepLinkText += "?"; + } else if (!prepLinkText.endsWith("?")) { + prepLinkText += "&"; + } + if (UtilValidate.isNotEmpty(queryString) && !"null".equals(queryString)) { + prepLinkText += queryString + "&"; + } + prepLinkText += viewSizeParam + "=" + viewSize + "&" + viewIndexParam + "="; + + String linkText; + + + // The current screenlet title bar navigation syling requires rendering + // these links in reverse order + // Last button + String lastLinkUrl = ""; + if (highIndex < listSize) { + int lastIndex = UtilMisc.getViewLastIndex(listSize, viewSize); + linkText = prepLinkText + lastIndex + anchor; + lastLinkUrl = rh.makeLink(request, response, linkText); + } + String nextLinkUrl = ""; + if (highIndex < listSize) { + linkText = prepLinkText + (viewIndex + 1) + anchor; + // - make the link + nextLinkUrl = rh.makeLink(request, response, linkText); + } + String previousLinkUrl = ""; + if (viewIndex > 0) { + linkText = prepLinkText + (viewIndex - 1) + anchor; + previousLinkUrl = rh.makeLink(request, response, linkText); + } + String firstLinkUrl = ""; + if (viewIndex > 0) { + linkText = prepLinkText + 0 + anchor; + firstLinkUrl = rh.makeLink(request, response, linkText); + } + + Map parameters = new HashMap<>(); + parameters.put("lowIndex", Paginator.getLowIndex(context)); + parameters.put("actualPageSize", actualPageSize); + parameters.put("listSize", listSize); + parameters.put("paginateLastStyle", modelForm.getPaginateLastStyle()); + parameters.put("lastLinkUrl", lastLinkUrl); + parameters.put("paginateLastLabel", modelForm.getPaginateLastLabel(context)); + parameters.put("paginateNextStyle", modelForm.getPaginateNextStyle()); + parameters.put("nextLinkUrl", nextLinkUrl); + parameters.put("paginateNextLabel", modelForm.getPaginateNextLabel(context)); + parameters.put("paginatePreviousStyle", modelForm.getPaginatePreviousStyle()); + parameters.put("paginatePreviousLabel", modelForm.getPaginatePreviousLabel(context)); + parameters.put("previousLinkUrl", previousLinkUrl); + parameters.put("paginateFirstStyle", modelForm.getPaginateFirstStyle()); + parameters.put("paginateFirstLabel", modelForm.getPaginateFirstLabel(context)); + parameters.put("firstLinkUrl", firstLinkUrl); + return parameters; + //this.output.putScreen("ScreenletPaginateMenu", parameters); + } + + // PortalPage is managed only for confMode="N" (show it) so renderer is only column management + // currently PortalPage send a ColunmContainer and portalColunm a column, + // and a container around each portlet, so very similar with a screen approach + // in future, portalPage should be deprecated because could be create some security issues and it's possible + // to manage same look and functionalities with screen and a dedicated sub-component in webtools + public void renderPortalPageBegin(Appendable writer, Map context, ModelScreenWidget.PortalPage portalPage) throws GeneralException, IOException { + String portalPageId = portalPage.getActualPortalPageId(context); + String originalPortalPageId = portalPage.getOriginalPortalPageId(context); + String confMode = portalPage.getConfMode(context); + + Map cb = new HashMap<>(); + cb.put("originalPortalPageId", originalPortalPageId); + cb.put("portalPageId", portalPageId); + cb.put("confMode", confMode); + if ("Y".equals(confMode)) { + throw new IOException("Render FrontJsScreen : include-portal-page with confMode=Y is used in a screen and it's not yet implemented" + + " portalPageId="+ portalPageId); + // this.output.pushScreen("PortalPageBegin", cb); + } + this.output.pushScreen("ColumnContainerBegin", new HashMap()); + } + + // CF renderPortalPageBegin + public void renderPortalPageEnd(Appendable writer, Map context, ModelScreenWidget.PortalPage portalPage) throws GeneralException, IOException { + // this.output.popScreen("PortalPageEnd"); should be re-activate if confMode should be manage + this.output.popScreen("ColumnContainerEnd"); + } + + // CF renderPortalPageBegin + public void renderPortalPageColumnBegin(Appendable writer, Map context, ModelScreenWidget.PortalPage portalPage, GenericValue portalPageColumn) throws GeneralException, IOException { + String portalPageId = portalPage.getActualPortalPageId(context); + String columnWidthPixels = portalPageColumn.getString("columnWidthPixels"); + + // manage only with grid system, so no absolue value + if (columnWidthPixels != null) { + Debug.logWarning("PortalPage with a column with width in pixel not null, FrontJs renderer manage only Width in percentage"+ + " portalPageid="+portalPageId, MODULE); + } + + Map cb = new HashMap<>(); + // first release very simple, to transform % to a grid class + String columnWidth; + Long columnWidthPercentage = portalPageColumn.getLong("columnWidthPercentage"); + if (columnWidthPercentage != null) { + if (columnWidthPercentage < 9) columnWidth = "md-1"; + else if (columnWidthPercentage < 17) columnWidth = "md-2"; + else if (columnWidthPercentage < 25) columnWidth = "md-3"; + else if (columnWidthPercentage < 34) columnWidth = "md-4"; + else if (columnWidthPercentage < 42) columnWidth = "md-5"; + else if (columnWidthPercentage < 51) columnWidth = "md-6"; + else if (columnWidthPercentage < 59) columnWidth = "md-7"; + else if (columnWidthPercentage < 67) columnWidth = "md-8"; + else if (columnWidthPercentage < 76) columnWidth = "md-9"; + else if (columnWidthPercentage < 84) columnWidth = "md-10"; + else if (columnWidthPercentage < 92) columnWidth = "md-11"; + else columnWidth = "md-12"; + cb.put("style", columnWidth); + } + this.output.pushScreen("ColumnBegin", cb); + } + + // CF renderPortalPageBegin + public void renderPortalPageColumnEnd(Appendable writer, Map context, ModelScreenWidget.PortalPage portalPage, GenericValue portalPageColumn) throws GeneralException, IOException { + this.output.popScreen("ColumnEnd"); + } + + // CF renderPortalPageBegin + public void renderPortalPagePortletBegin(Appendable writer, Map context, ModelScreenWidget.PortalPage portalPage, GenericValue portalPortlet) throws GeneralException, IOException { + String portalPageId = portalPage.getActualPortalPageId(context); + String portalPortletId = portalPortlet.getString("portalPortletId"); + String portletSeqId = portalPortlet.getString("portletSeqId"); + + Map parameters = new HashMap<>(); + parameters.put("id",portalPortletId+"-"+portletSeqId); + if (portalPortlet.containsKey("watcherName")){ + parameters.put("autoUpdateTarget", "showPortletFj/"+portalPageId+"/"+portalPortletId+"/"+portletSeqId); + String watcherName = portalPortlet.getString("watcherName"); + if (UtilValidate.isNotEmpty(watcherName)) { + List watcherList = StringUtil.split(watcherName,","); + if (watcherList.size()>1) { + StringBuilder watcherNameStr = new StringBuilder(); + watcherNameStr.append(watcherList.get(0).trim()); + for (int i = 1; i < watcherList.size(); i++) { + watcherNameStr.append("-"); + watcherNameStr.append(watcherList.get(i).trim()); + } + parameters.put("watcherName", watcherNameStr.toString()); + } else { + parameters.put("watcherName", watcherName); + } + } + } + this.output.pushScreen("ContainerOpen", parameters); + } + + public void renderPortalPagePortletEnd(Appendable writer, Map context, ModelScreenWidget.PortalPage portalPage, GenericValue portalPortlet) throws GeneralException, IOException { + this.output.popScreen("ContainerClose"); + } + + public void renderPortalPagePortletBody(Appendable writer, Map context, ModelScreenWidget.PortalPage portalPage, GenericValue portalPortlet) throws GeneralException, IOException { + String portalPortletId = portalPortlet.getString("portalPortletId"); + String portletSeqId = portalPortlet.getString("portletSeqId"); + String screenName = portalPortlet.getString("screenName"); + String screenLocation = portalPortlet.getString("screenLocation"); + context.put("portalPortletId", portalPortletId); + context.put("portletSeqId", portletSeqId); + context.put("currentAreaId", portalPortletId + "-" + portletSeqId); + + ModelScreen modelScreen = null; + if (UtilValidate.isNotEmpty(screenName) && UtilValidate.isNotEmpty(screenLocation)) { + try { + modelScreen = ScreenFactory.getScreenFromLocation(screenLocation, screenName); + } catch (IOException | SAXException | ParserConfigurationException e) { + String errMsg = "Error rendering portlet ID [" + portalPortletId + "]: " + e.toString(); + Debug.logError(e, errMsg, MODULE); + throw new RuntimeException(errMsg); + } + } + if (writer != null && context != null) { + modelScreen.renderScreenString(writer, context, this); + } else { + Debug.logError("Null on some Path: writer" + writer + ", context: " + context, MODULE); + } + } + + @Override + public void renderColumnContainer(Appendable writer, Map context, ColumnContainer columnContainer) throws IOException { + String id = columnContainer.getId(context); + String style = columnContainer.getStyle(context); + Map parameters = new HashMap<>(); + if (UtilValidate.isNotEmpty(id)) parameters.put("id", id); + if (UtilValidate.isNotEmpty(style)) parameters.put("style", style); + this.output.pushScreen("ColumnContainerBegin", parameters); + for (ModelScreenWidget.Column column : columnContainer.getColumns()) { + id = column.getId(context); + style = column.getStyle(context); + parameters = new HashMap<>(); + if (UtilValidate.isNotEmpty(id)) parameters.put("id", id); + if (UtilValidate.isNotEmpty(style)) parameters.put("style", style); + this.output.pushScreen("ColumnBegin", parameters); + for (ModelScreenWidget subWidget : column.getSubWidgets()) { + try { + subWidget.renderWidgetString(writer, context, this); + } catch (GeneralException e) { + throw new IOException(e); + } + } + this.output.popScreen("ColumnEnd"); + } + this.output.popScreen("ColumnContainerEnd"); + } + +} diff --git a/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsScreenViewHandler.java b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsScreenViewHandler.java new file mode 100644 index 000000000..185659320 --- /dev/null +++ b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsScreenViewHandler.java @@ -0,0 +1,133 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +package org.apache.ofbiz.widget.renderer.frontjs; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.ofbiz.base.lang.JSON; +import org.apache.ofbiz.base.util.Debug; +import org.apache.ofbiz.base.util.GeneralException; +import org.apache.ofbiz.base.util.UtilCodec; +import org.apache.ofbiz.base.util.UtilHttp; +import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.base.util.collections.MapStack; +import org.apache.ofbiz.webapp.view.AbstractViewHandler; +import org.apache.ofbiz.webapp.view.ViewHandlerException; +import org.apache.ofbiz.widget.model.ModelTheme; +import org.apache.ofbiz.widget.renderer.FormStringRenderer; +import org.apache.ofbiz.widget.renderer.MenuStringRenderer; +import org.apache.ofbiz.widget.renderer.ScreenRenderer; +import org.apache.ofbiz.widget.renderer.ScreenStringRenderer; +import org.apache.ofbiz.widget.renderer.TreeStringRenderer; +import org.apache.ofbiz.widget.renderer.VisualTheme; +import org.xml.sax.SAXException; + +import freemarker.template.TemplateException; +import freemarker.template.utility.StandardCompress; + + +public class FrontJsScreenViewHandler extends AbstractViewHandler { + + private static final String MODULE = FrontJsScreenViewHandler.class.getName(); + + protected ServletContext servletContext = null; + + @Override + public void init(ServletContext context) { + this.servletContext = context; + } + + private ScreenStringRenderer loadRenderers(FrontJsOutput output, + HttpServletRequest request, HttpServletResponse response, + Map context) { + ScreenStringRenderer screenStringRenderer = new FrontJsScreenRenderer(getName(), output); + FormStringRenderer formStringRenderer = new FrontJsFormRenderer(output, request, response); + context.put("formStringRenderer", formStringRenderer); + + TreeStringRenderer treeStringRenderer = new FrontJsTreeRenderer(output); + context.put("treeStringRenderer", treeStringRenderer); + MenuStringRenderer menuStringRenderer = new FrontJsMenuRenderer(output, request, response); + context.put("menuStringRenderer", menuStringRenderer); + return screenStringRenderer; + } + + @Override + public void render(String name, String page, String info, String contentType, String encoding, HttpServletRequest request, HttpServletResponse response) throws ViewHandlerException { + try { + Writer writer = response.getWriter(); + VisualTheme visualTheme = UtilHttp.getVisualTheme(request); + ModelTheme modelTheme = visualTheme.getModelTheme(); + // compress output if configured to do so + if (UtilValidate.isEmpty(encoding)) { + encoding = modelTheme.getEncoding(getName()); + } + boolean compressOutput = "compressed".equals(encoding); + if (!compressOutput) { + compressOutput = "true".equals(modelTheme.getCompress(getName())); + } + if (!compressOutput && this.servletContext != null) { + compressOutput = "true".equals(this.servletContext.getAttribute("compressHTML")); + } + if (compressOutput) { + // StandardCompress defaults to a 2k buffer. That could be increased + // to speed up output. + writer = new StandardCompress().getWriter(writer, null); + } + // writer will be not used during renderer, but only at the end of this method to send json result (the frontJsOutput) + // during all renderer process it's frontJsOutput which will be completed. + FrontJsOutput frontJsOutput = new FrontJsOutput(name); + MapStack context = MapStack.create(); + ScreenRenderer.populateContextForRequest(context, null, request, response, servletContext); + ScreenStringRenderer screenStringRenderer = loadRenderers(frontJsOutput, request, response, context); + ScreenRenderer screens = new ScreenRenderer(writer, context, screenStringRenderer); + context.put("screens", screens); + context.put("simpleEncoder", UtilCodec.getEncoder(visualTheme.getModelTheme().getEncoder(getName()))); + screenStringRenderer.renderScreenBegin(writer, context); + screens.render(page); + screenStringRenderer.renderScreenEnd(writer, context); + + JSON json = JSON.from(frontJsOutput.output()); + String jsonStr = json.toString(); + // set the JSON content type + response.setContentType("application/json"); + // jsonStr.length is not reliable for unicode characters + response.setContentLength(jsonStr.getBytes("UTF8").length); + writer.write(jsonStr); + + writer.flush(); + } catch (TemplateException e) { + Debug.logError(e, "Error initializing screen renderer", MODULE); + throw new ViewHandlerException(e.getMessage()); + } catch (IOException e) { + throw new ViewHandlerException("Error in the response writer/output stream: " + e.toString(), e); + } catch (SAXException | ParserConfigurationException e) { + throw new ViewHandlerException("XML Error rendering page: " + e.toString(), e); + } catch (GeneralException e) { + throw new ViewHandlerException("Lower level error rendering page: " + e.toString(), e); + } + } +} diff --git a/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsTreeRenderer.java b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsTreeRenderer.java new file mode 100644 index 000000000..7dc64a951 --- /dev/null +++ b/vuejs/src/main/java/org/apache/ofbiz/widget/renderer/frontjs/FrontJsTreeRenderer.java @@ -0,0 +1,256 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +package org.apache.ofbiz.widget.renderer.frontjs; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.ofbiz.base.util.StringUtil; +import org.apache.ofbiz.base.util.UtilGenerics; +import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.webapp.control.RequestHandler; +import org.apache.ofbiz.webapp.taglib.ContentUrlTag; +import org.apache.ofbiz.widget.WidgetWorker; +import org.apache.ofbiz.widget.model.ModelTree; +import org.apache.ofbiz.widget.renderer.ScreenRenderer; +import org.apache.ofbiz.widget.renderer.ScreenStringRenderer; +import org.apache.ofbiz.widget.renderer.TreeStringRenderer; + +public class FrontJsTreeRenderer implements TreeStringRenderer { + + private static final String MODULE = FrontJsTreeRenderer.class.getName(); + private FrontJsOutput output; + FrontJsTreeRenderer(FrontJsOutput output) { + this.output = output; + } + public void renderNodeBegin(Appendable writer, Map context, ModelTree.ModelNode node, int depth) throws IOException { + String currentNodeTrailPiped; + Object obj = context.get("currentNodeTrail"); + List currentNodeTrail = (obj instanceof List) ? UtilGenerics.cast(obj) : null; + + String style = ""; + if (node.isRootNode()) { + style = "basic-tree"; + } + + Map cb = new HashMap<>(); + cb.put("style", style); + + this.output.pushScreen("NodeBegin", cb); + + String pkName = node.getPkName(context); + String entityId; + String entryName = node.getEntryName(); + if (UtilValidate.isNotEmpty(entryName)) { + Map map = UtilGenerics.cast(context.get(entryName)); + entityId = map.get(pkName); + } else { + entityId = (String) context.get(pkName); + } + boolean hasChildren = node.hasChildren(context); + + // check to see if this node needs to be expanded. + if (hasChildren && node.isExpandCollapse()) { + // FIXME: Using a widget model in this way is an ugly hack. + ModelTree.ModelNode.Link expandCollapseLink = null; + String targetEntityId = null; + Object obj1 = context.get("targetNodeTrail"); + List targetNodeTrail = (obj1 instanceof List) ? UtilGenerics.cast(obj1) : null; + if (depth < targetNodeTrail.size()) { + targetEntityId = targetNodeTrail.get(depth); + } + + int openDepth = node.getModelTree().getOpenDepth(); + if (depth >= openDepth && (targetEntityId == null || !targetEntityId.equals(entityId))) { + // Not on the trail + if (node.showPeers(depth, context)) { + context.put("processChildren", Boolean.FALSE); + currentNodeTrailPiped = StringUtil.join(currentNodeTrail, "|"); + StringBuilder target = new StringBuilder(node.getModelTree().getExpandCollapseRequest(context)); + String trailName = node.getModelTree().getTrailName(context); + if (target.indexOf("?") < 0) { + target.append("?"); + } else { + target.append("&"); + } + target.append(trailName).append("=").append(currentNodeTrailPiped); + expandCollapseLink = new ModelTree.ModelNode.Link("collapsed", target.toString(), " "); + } + } else { + context.put("processChildren", Boolean.TRUE); + String lastContentId = currentNodeTrail.remove(currentNodeTrail.size() - 1); + currentNodeTrailPiped = StringUtil.join(currentNodeTrail, "|"); + if (currentNodeTrailPiped == null) { + currentNodeTrailPiped = ""; + } + StringBuilder target = new StringBuilder(node.getModelTree().getExpandCollapseRequest(context)); + String trailName = node.getModelTree().getTrailName(context); + if (target.indexOf("?") < 0) { + target.append("?"); + } else { + target.append("&"); + } + target.append(trailName).append("=").append(currentNodeTrailPiped); + expandCollapseLink = new ModelTree.ModelNode.Link("expanded", target.toString(), " "); + // add it so it can be remove in renderNodeEnd + currentNodeTrail.add(lastContentId); + } + if (expandCollapseLink != null) { + renderLink(writer, context, expandCollapseLink); + } + } else if (!hasChildren) { + context.put("processChildren", Boolean.FALSE); + ModelTree.ModelNode.Link expandCollapseLink = new ModelTree.ModelNode.Link("leafnode", "", " "); + renderLink(writer, context, expandCollapseLink); + } + } + + public void renderNodeEnd(Appendable writer, Map context, ModelTree.ModelNode node) { + Boolean processChildren = (Boolean) context.get("processChildren"); + Map cb = new HashMap<>(); + cb.put("processChildren", Boolean.toString(processChildren)); + cb.put("isRootNode", Boolean.toString(node.isRootNode())); + HashMap hashMapStringObject = new HashMap<>(); + hashMapStringObject.put("NodeEnd", cb); + this.output.popScreen("NodeEnd"); + } + + public void renderLastElement(Appendable writer, Map context, ModelTree.ModelNode node) { + Boolean processChildren = (Boolean) context.get("processChildren"); + if (processChildren) { + Map cb = new HashMap<>(); + cb.put("style", "basic-tree"); + this.output.putScreen("LastElement", cb); + } + } + + public void renderLabel(Appendable writer, Map context, ModelTree.ModelNode.Label label) { + String id = label.getId(context); + String style = label.getStyle(context); + String labelText = label.getText(context); + + Map cb = new HashMap<>(); + cb.put("id", id); + cb.put("style", style); + cb.put("labelText", labelText); + this.output.putScreen("Label", cb); + } + + public void renderLink(Appendable writer, Map context, ModelTree.ModelNode.Link link) throws IOException { + String target = link.getTarget(context); + StringBuilder linkUrl = new StringBuilder(); + HttpServletResponse response = (HttpServletResponse) context.get("response"); + HttpServletRequest request = (HttpServletRequest) context.get("request"); + + if (UtilValidate.isNotEmpty(target)) { + WidgetWorker.buildHyperlinkUrl(linkUrl, target, link.getUrlMode(), link.getParameterMap(context), link.getPrefix(context), + link.getFullPath(), link.getSecure(), link.getEncode(), request, response, context); + } + + String id = link.getId(context); + String style = link.getStyle(context); + String name = link.getName(context); + String title = link.getTitle(context); + String targetWindow = link.getTargetWindow(context); + String linkText = link.getText(context); + + String imgStr = ""; + ModelTree.ModelNode.Image img = link.getImage(); + if (img != null) { + StringWriter sw = new StringWriter(); + renderImage(writer, context, img); + imgStr = sw.toString(); + } + + Map cb = new HashMap<>(); + cb.put("id", id); + cb.put("style", style); + cb.put("name", name); + cb.put("title", title); + cb.put("targetWindow", targetWindow); + cb.put("linkUrl", linkUrl); + cb.put("linkText", linkText); + cb.put("imgStr", imgStr.replaceAll("\"", "\\\\\"")); + this.output.putScreen("Link", cb); + } + + public void renderImage(Appendable writer, Map context, ModelTree.ModelNode.Image image) { + if (image == null) { + return; + } + HttpServletResponse response = (HttpServletResponse) context.get("response"); + HttpServletRequest request = (HttpServletRequest) context.get("request"); + + String urlMode = image.getUrlMode(); + String src = image.getSrc(context); + String id = image.getId(context); + String style = image.getStyle(context); + String wid = image.getWidth(context); + String hgt = image.getHeight(context); + String border = image.getBorder(context); + String alt = ""; //TODO add alt to tree images image.getAlt(context); + + String urlString = ""; + + if ("intra-app".equalsIgnoreCase(urlMode)) { + if (request != null && response != null) { + ServletContext ctx = (ServletContext) request.getAttribute("servletContext"); + RequestHandler rh = (RequestHandler) ctx.getAttribute("_REQUEST_HANDLER_"); + urlString = rh.makeLink(request, response, src, false, false, false); + } else { + urlString = src; + } + } else if ("content".equalsIgnoreCase(urlMode)) { + if (request != null && response != null) { + StringBuilder newURL = new StringBuilder(); + ContentUrlTag.appendContentPrefix(request, newURL); + newURL.append(src); + urlString = newURL.toString(); + } + } else { + urlString = src; + } + Map cb = new HashMap<>(); + cb.put("src", src); + cb.put("id", id); + cb.put("style", style); + cb.put("wid", wid); + cb.put("hgt", hgt); + cb.put("border", border); + cb.put("alt", alt); + cb.put("urlString", urlString); + this.output.putScreen("Image", cb); + } + + public ScreenStringRenderer getScreenStringRenderer(Map context) { + ScreenRenderer screenRenderer = (ScreenRenderer)context.get("screens"); + if (screenRenderer != null) { + return screenRenderer.getScreenStringRenderer(); + } + return null; + } +} diff --git a/vuejs/template/vuejsStart.ftl b/vuejs/template/vuejsStart.ftl new file mode 100644 index 000000000..3739278fc --- /dev/null +++ b/vuejs/template/vuejsStart.ftl @@ -0,0 +1,4 @@ +
+ + +
diff --git a/vuejs/tools/applyOfbiz2CommitPatchs.sh b/vuejs/tools/applyOfbiz2CommitPatchs.sh new file mode 100755 index 000000000..b588e8fd4 --- /dev/null +++ b/vuejs/tools/applyOfbiz2CommitPatchs.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +cd ../../.. +patchs="$(find ./plugins/vuejs/ofbizCommit2add/ -name 'OFBIZ*.patch' |sort -n)" +for file in $patchs; +do +git am $file +done \ No newline at end of file diff --git a/vuejs/webapp/vuejs/.browserslistrc b/vuejs/webapp/vuejs/.browserslistrc new file mode 100644 index 000000000..d6471a38c --- /dev/null +++ b/vuejs/webapp/vuejs/.browserslistrc @@ -0,0 +1,2 @@ +> 1% +last 2 versions diff --git a/vuejs/webapp/vuejs/.eslintrc.js b/vuejs/webapp/vuejs/.eslintrc.js new file mode 100644 index 000000000..1c6179f37 --- /dev/null +++ b/vuejs/webapp/vuejs/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + root: true, + env: { + node: true + }, + 'extends': [ + 'plugin:vue/essential', + 'eslint:recommended' + ], + rules: { + 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' + }, + parserOptions: { + parser: 'babel-eslint' + } +} diff --git a/vuejs/webapp/vuejs/.gitignore b/vuejs/webapp/vuejs/.gitignore new file mode 100644 index 000000000..2c2d596e7 --- /dev/null +++ b/vuejs/webapp/vuejs/.gitignore @@ -0,0 +1,24 @@ +.DS_Store +node_modules +/dist + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# conf files +package-lock.json diff --git a/vuejs/webapp/vuejs/README.md b/vuejs/webapp/vuejs/README.md new file mode 100644 index 000000000..d4a2a7d41 --- /dev/null +++ b/vuejs/webapp/vuejs/README.md @@ -0,0 +1,29 @@ +# vuejs + +## Project setup +``` +npm install +``` + +### Compiles and hot-reloads for development +``` +npm run serve +``` + +### Compiles and minifies for production +``` +npm run build +``` + +### Run your tests +``` +npm run test +``` + +### Lints and fixes files +``` +npm run lint +``` + +### Customize configuration +See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/vuejs/webapp/vuejs/WEB-INF/web.xml b/vuejs/webapp/vuejs/WEB-INF/web.xml new file mode 100644 index 000000000..0c7f2d1f4 --- /dev/null +++ b/vuejs/webapp/vuejs/WEB-INF/web.xml @@ -0,0 +1,36 @@ + + + + + + VueJs FrontJs Module of the Apache OFBiz Project + + ControlFilter + ControlFilter + org.apache.ofbiz.webapp.control.ControlFilter + + allowedPaths + /dist + + + + dist/index.html + + diff --git a/vuejs/webapp/vuejs/babel.config.js b/vuejs/webapp/vuejs/babel.config.js new file mode 100644 index 000000000..e9558405f --- /dev/null +++ b/vuejs/webapp/vuejs/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/cli-plugin-babel/preset' + ] +} diff --git a/vuejs/webapp/vuejs/package.json b/vuejs/webapp/vuejs/package.json new file mode 100644 index 000000000..f42d86671 --- /dev/null +++ b/vuejs/webapp/vuejs/package.json @@ -0,0 +1,52 @@ +{ + "name": "vuejs", + "version": "1.0.0", + "private": true, + "description": "A Vue.js project", + "author": "bashark ", + "scripts": { + "serve": "vue-cli-service serve", + "lint": "vue-cli-service lint", + "build-default": "vue-cli-service build", + "build-dev": "vue-cli-service build --mode development --name vuejs 'src/main.js'", + "build-dev-watch": "vue-cli-service build --mode development --watch --name vuejs 'src/main.js'", + "build-prod": "vue-cli-service build --mode production --name vuejs 'src/main.js'", + "build-prod-watch": "vue-cli-service build --mode production --watch --name vuejs 'src/main.js'" + }, + "dependencies": { + "@babel/polyfill": "^7.8.7", + "core-js": "^3.3.2", + "epic-spinners": "^1.1.0", + "lodash": "^4.17.15", + "query-string": "^6.11.1", + "stylus": "^0.54.7", + "stylus-loader": "^3.0.2", + "terser": "^4.6.7", + "vue": "^2.6.10", + "vue-blockui": "^1.1.7", + "vue-resource": "^1.5.1", + "vue-router": "^3.1.6", + "vue-the-mask": "^0.11.1", + "vue-wait": "^1.4.6", + "vuetify": "^2.2.18", + "vuex": "^3.1.3", + "vuex-strong-cache": "^0.1.1" + }, + "devDependencies": { + "@mdi/font": "^4.9.95", + "@mdi/js": "^5.0.45", + "@vue/cli-plugin-babel": "^4.2.3", + "@vue/cli-plugin-eslint": "^4.2.3", + "@vue/cli-service": "^4.2.3", + "babel-eslint": "^10.1.0", + "eslint": "^5.16.0", + "eslint-plugin-vue": "^5.0.0", + "sass": "^1.26.3", + "sass-loader": "^7.1.0", + "terser-webpack-plugin": "^2.3.5", + "uglifyjs-webpack-plugin": "^2.2.0", + "vue-cli-plugin-vuetify": "^1.1.1", + "vue-template-compiler": "^2.6.10", + "vuetify-loader": "^1.4.3" + } +} diff --git a/vuejs/webapp/vuejs/postcss.config.js b/vuejs/webapp/vuejs/postcss.config.js new file mode 100644 index 000000000..5bfb8f628 --- /dev/null +++ b/vuejs/webapp/vuejs/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {} + } +}; diff --git a/vuejs/webapp/vuejs/src/components/App.vue b/vuejs/webapp/vuejs/src/components/App.vue new file mode 100644 index 000000000..580ca9872 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/App.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/AppCpt.vue b/vuejs/webapp/vuejs/src/components/AppCpt.vue new file mode 100644 index 000000000..26e9a60db --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/AppCpt.vue @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/vuejs/webapp/vuejs/src/components/AppDialog.vue b/vuejs/webapp/vuejs/src/components/AppDialog.vue new file mode 100644 index 000000000..9df29fa78 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/AppDialog.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/AppLog.vue b/vuejs/webapp/vuejs/src/components/AppLog.vue new file mode 100644 index 000000000..b5b184903 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/AppLog.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/AppMenu.vue b/vuejs/webapp/vuejs/src/components/AppMenu.vue new file mode 100644 index 000000000..ada976096 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/AppMenu.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/AppWait.vue b/vuejs/webapp/vuejs/src/components/AppWait.vue new file mode 100644 index 000000000..1acaf55b6 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/AppWait.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/Portal.vue b/vuejs/webapp/vuejs/src/components/Portal.vue new file mode 100644 index 000000000..3e95e39d7 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/Portal.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/Screen.vue b/vuejs/webapp/vuejs/src/components/Screen.vue new file mode 100644 index 000000000..2aa13825e --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/Screen.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/SeleniumInfoPanel.vue b/vuejs/webapp/vuejs/src/components/SeleniumInfoPanel.vue new file mode 100644 index 000000000..be336082d --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/SeleniumInfoPanel.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueAsterisks.vue b/vuejs/webapp/vuejs/src/components/VueAsterisks.vue new file mode 100644 index 000000000..e61e1b4f2 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueAsterisks.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueColumn.vue b/vuejs/webapp/vuejs/src/components/VueColumn.vue new file mode 100644 index 000000000..04bf952f7 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueColumn.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueColumnContainer.vue b/vuejs/webapp/vuejs/src/components/VueColumnContainer.vue new file mode 100644 index 000000000..8fb3f4ea6 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueColumnContainer.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueColumnPortlet.vue b/vuejs/webapp/vuejs/src/components/VueColumnPortlet.vue new file mode 100644 index 000000000..5057552d4 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueColumnPortlet.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueContainer.vue b/vuejs/webapp/vuejs/src/components/VueContainer.vue new file mode 100644 index 000000000..33dbc7e3c --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueContainer.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueDateTimeField.vue b/vuejs/webapp/vuejs/src/components/VueDateTimeField.vue new file mode 100644 index 000000000..330a8c79e --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueDateTimeField.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueDisplayField.vue b/vuejs/webapp/vuejs/src/components/VueDisplayField.vue new file mode 100644 index 000000000..5de38d198 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueDisplayField.vue @@ -0,0 +1,181 @@ +// TODO compare size and description.size and tronque + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueDropDownField.vue b/vuejs/webapp/vuejs/src/components/VueDropDownField.vue new file mode 100644 index 000000000..28c88e43e --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueDropDownField.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueError.vue b/vuejs/webapp/vuejs/src/components/VueError.vue new file mode 100644 index 000000000..c7af0d9b2 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueError.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueField.vue b/vuejs/webapp/vuejs/src/components/VueField.vue new file mode 100644 index 000000000..0d9ed09c8 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueField.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueFieldGroup.vue b/vuejs/webapp/vuejs/src/components/VueFieldGroup.vue new file mode 100644 index 000000000..f18c6bbbd --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueFieldGroup.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueFieldRow.vue b/vuejs/webapp/vuejs/src/components/VueFieldRow.vue new file mode 100644 index 000000000..fdb69d7e5 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueFieldRow.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueFieldRowTitleCell.vue b/vuejs/webapp/vuejs/src/components/VueFieldRowTitleCell.vue new file mode 100644 index 000000000..df256676c --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueFieldRowTitleCell.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueFieldRowWidgetCell.vue b/vuejs/webapp/vuejs/src/components/VueFieldRowWidgetCell.vue new file mode 100644 index 000000000..444533f00 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueFieldRowWidgetCell.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueFieldTitle.vue b/vuejs/webapp/vuejs/src/components/VueFieldTitle.vue new file mode 100644 index 000000000..5d059c5eb --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueFieldTitle.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueForm.vue b/vuejs/webapp/vuejs/src/components/VueForm.vue new file mode 100644 index 000000000..4390b97c6 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueForm.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueFormatEmptySpace.vue b/vuejs/webapp/vuejs/src/components/VueFormatEmptySpace.vue new file mode 100644 index 000000000..5a5fce045 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueFormatEmptySpace.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueHeader.vue b/vuejs/webapp/vuejs/src/components/VueHeader.vue new file mode 100644 index 000000000..7167b9a00 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueHeader.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueHeaderRow.vue b/vuejs/webapp/vuejs/src/components/VueHeaderRow.vue new file mode 100644 index 000000000..27df18b80 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueHeaderRow.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueHeaderRowCell.vue b/vuejs/webapp/vuejs/src/components/VueHeaderRowCell.vue new file mode 100644 index 000000000..47f3153f4 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueHeaderRowCell.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueHiddenField.vue b/vuejs/webapp/vuejs/src/components/VueHiddenField.vue new file mode 100644 index 000000000..b470939df --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueHiddenField.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueHorizontalSeparator.vue b/vuejs/webapp/vuejs/src/components/VueHorizontalSeparator.vue new file mode 100644 index 000000000..9c6c527fe --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueHorizontalSeparator.vue @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/vuejs/webapp/vuejs/src/components/VueHyperlinkField.vue b/vuejs/webapp/vuejs/src/components/VueHyperlinkField.vue new file mode 100644 index 000000000..b82c2c139 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueHyperlinkField.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueItemRow.vue b/vuejs/webapp/vuejs/src/components/VueItemRow.vue new file mode 100644 index 000000000..0cde3a645 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueItemRow.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueItemRowCell.vue b/vuejs/webapp/vuejs/src/components/VueItemRowCell.vue new file mode 100644 index 000000000..151459a3f --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueItemRowCell.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueLabel.vue b/vuejs/webapp/vuejs/src/components/VueLabel.vue new file mode 100644 index 000000000..0535c6403 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueLabel.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueLink.vue b/vuejs/webapp/vuejs/src/components/VueLink.vue new file mode 100644 index 000000000..3e4464324 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueLink.vue @@ -0,0 +1,264 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueListWrapper.vue b/vuejs/webapp/vuejs/src/components/VueListWrapper.vue new file mode 100644 index 000000000..268f90392 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueListWrapper.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueLogin.vue b/vuejs/webapp/vuejs/src/components/VueLogin.vue new file mode 100644 index 000000000..93798f997 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueLogin.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueLookupField.vue b/vuejs/webapp/vuejs/src/components/VueLookupField.vue new file mode 100644 index 000000000..6710bbced --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueLookupField.vue @@ -0,0 +1,299 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueMenu.vue b/vuejs/webapp/vuejs/src/components/VueMenu.vue new file mode 100644 index 000000000..5d24d1d75 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueMenu.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueMenuItem.vue b/vuejs/webapp/vuejs/src/components/VueMenuItem.vue new file mode 100644 index 000000000..1ed92a4f1 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueMenuItem.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueMessageList.vue b/vuejs/webapp/vuejs/src/components/VueMessageList.vue new file mode 100644 index 000000000..8cf46b185 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueMessageList.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueNavMenu.vue b/vuejs/webapp/vuejs/src/components/VueNavMenu.vue new file mode 100644 index 000000000..1006fd2f3 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueNavMenu.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueNavMenuItem.vue b/vuejs/webapp/vuejs/src/components/VueNavMenuItem.vue new file mode 100644 index 000000000..e0366a801 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueNavMenuItem.vue @@ -0,0 +1,64 @@ + + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueNavMenuItemInline.vue b/vuejs/webapp/vuejs/src/components/VueNavMenuItemInline.vue new file mode 100644 index 000000000..a4d8853d0 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueNavMenuItemInline.vue @@ -0,0 +1,61 @@ + + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueNextPrev.vue b/vuejs/webapp/vuejs/src/components/VueNextPrev.vue new file mode 100644 index 000000000..099092e3c --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueNextPrev.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueOption.vue b/vuejs/webapp/vuejs/src/components/VueOption.vue new file mode 100644 index 000000000..246ca7067 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueOption.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VuePasswordField.vue b/vuejs/webapp/vuejs/src/components/VuePasswordField.vue new file mode 100644 index 000000000..2d013ce19 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VuePasswordField.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VuePlatformSpecific.vue b/vuejs/webapp/vuejs/src/components/VuePlatformSpecific.vue new file mode 100644 index 000000000..ba07b9ba8 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VuePlatformSpecific.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VuePortlet.vue b/vuejs/webapp/vuejs/src/components/VuePortlet.vue new file mode 100644 index 000000000..74126f923 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VuePortlet.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueRadioField.vue b/vuejs/webapp/vuejs/src/components/VueRadioField.vue new file mode 100644 index 000000000..291b1f877 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueRadioField.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueScreenlet.vue b/vuejs/webapp/vuejs/src/components/VueScreenlet.vue new file mode 100644 index 000000000..e6fe47374 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueScreenlet.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueSingleWrapper.vue b/vuejs/webapp/vuejs/src/components/VueSingleWrapper.vue new file mode 100644 index 000000000..01aa261b5 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueSingleWrapper.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueSortField.vue b/vuejs/webapp/vuejs/src/components/VueSortField.vue new file mode 100644 index 000000000..24c6c8772 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueSortField.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueSubmitField.vue b/vuejs/webapp/vuejs/src/components/VueSubmitField.vue new file mode 100644 index 000000000..1034c0597 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueSubmitField.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueTable.vue b/vuejs/webapp/vuejs/src/components/VueTable.vue new file mode 100644 index 000000000..cd6b51afc --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueTable.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueTextAreaField.vue b/vuejs/webapp/vuejs/src/components/VueTextAreaField.vue new file mode 100644 index 000000000..3d3dfa7cd --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueTextAreaField.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueTextField.vue b/vuejs/webapp/vuejs/src/components/VueTextField.vue new file mode 100644 index 000000000..ead17c1c1 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueTextField.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueTextFindField.vue b/vuejs/webapp/vuejs/src/components/VueTextFindField.vue new file mode 100644 index 000000000..0f319d027 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueTextFindField.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueThead.vue b/vuejs/webapp/vuejs/src/components/VueThead.vue new file mode 100644 index 000000000..10a9ab6d1 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueThead.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/VueTr.vue b/vuejs/webapp/vuejs/src/components/VueTr.vue new file mode 100644 index 000000000..1accc9518 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/VueTr.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech.vue b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech.vue new file mode 100644 index 000000000..db8c5accd --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech.vue @@ -0,0 +1,1178 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/EmailAddress.vue b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/EmailAddress.vue new file mode 100644 index 000000000..bd9a09d52 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/EmailAddress.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/FtpAddress.vue b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/FtpAddress.vue new file mode 100644 index 000000000..d29ebee2d --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/FtpAddress.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/Generic.vue b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/Generic.vue new file mode 100644 index 000000000..b065cdcda --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/Generic.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/PostalAddress.vue b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/PostalAddress.vue new file mode 100644 index 000000000..ece2942c5 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/PostalAddress.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/TelecomNumber.vue b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/TelecomNumber.vue new file mode 100644 index 000000000..09b1aac24 --- /dev/null +++ b/vuejs/webapp/vuejs/src/components/platformSpecific/ContactMech/TelecomNumber.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/vuejs/webapp/vuejs/src/js/constants.js b/vuejs/webapp/vuejs/src/js/constants.js new file mode 100644 index 000000000..340dba938 --- /dev/null +++ b/vuejs/webapp/vuejs/src/js/constants.js @@ -0,0 +1,59 @@ +export default { + apiUrl: 'https://localhost:8443/examplefjs/control', + formApiUrl: 'https://localhost:8443/example/control', + hostUrl: 'https://localhost:8443', + main: {path: '/main', tokenRequired: false}, + login: {path: '/loginJson', tokenRequired: false}, + ping: {path: '/ping', tokenRequired: false}, + ajaxCheckLogin: {path: '/ajaxCheckLogin', tokenRequired: false}, + showPortlet: {path: '/showPortletFj', tokenRequired: false}, + getCommonUiLabel: '/getCommonUiLabel', + + blockUi: true, + log: true, + + components: { + Column: 'vue-column', + ColumnContainer: 'vue-column-container', + Container: 'vue-container', + ContentBegin: 'vue-error', + DateTimeField: 'vue-date-time-field', + DisplayField: 'vue-display-field', + DropDownField: 'vue-drop-down-field', + FieldGroup: 'vue-field-group', // todo + FieldRow: 'vue-field-row', + FieldRowTitleCell: 'vue-field-row-title-cell', + FieldRowWidgetCell: 'vue-field-row-widget-cell', + Form: 'vue-form', + FormatEmptySpace: 'vue-format-empty-space', + Header: 'vue-header', + HeaderRow: 'vue-header-row', + HeaderRowCell: 'vue-header-row-cell', + FieldTitle: 'vue-field-title', + HiddenField: 'vue-hidden-field', + HorizontalSeparator: 'vue-horizontal-separator', + HyperlinkField: 'vue-hyperlink-field', + Image: 'vue-error', // not yet implementetd + ItemRow: 'vue-item-row', + ItemRowCell: 'vue-item-row-cell', + Label: 'vue-label', + Link: 'vue-link', + ListWrapper: 'vue-list-wrapper', + LookupField: 'vue-lookup-field', + Menu: 'vue-menu', + MenuItem: 'vue-menu-item', + NextPrev: 'vue-next-prev', + PasswordField: 'vue-password-field', + PortalPagePortlet: 'vue-portlet', + RadioField: 'vue-radio-field', + Screenlet: 'vue-screenlet', + SingleWrapper: 'vue-single-wrapper', + SortField: 'vue-sort-field', + SubmitField: 'vue-submit-field', + test: 'test', + TextAreaField: 'vue-text-area-field', + TextField: 'vue-text-field', + TextFindField: 'vue-text-find-field', + VueJs: 'vue-platform-specific' + } +}; diff --git a/vuejs/webapp/vuejs/src/js/icons.js b/vuejs/webapp/vuejs/src/js/icons.js new file mode 100644 index 000000000..25391b056 --- /dev/null +++ b/vuejs/webapp/vuejs/src/js/icons.js @@ -0,0 +1,99 @@ +import { + mdiAccountArrowRight, + mdiAccountCancel, + mdiAccountGroup, + mdiArrowCollapse, + mdiArrowCollapseLeft, + mdiArrowCollapseRight, + mdiArrowCollapseUp, + mdiArrowExpand, + mdiArrowExpandDown, + mdiArrowExpandLeft, + mdiArrowExpandRight, + mdiArrowLeft, + mdiArrowRight, + mdiAt, + mdiBookAccount, + mdiCashMultiple, + mdiCheck, + mdiClockEnd, + mdiClose, + mdiDelete, + mdiDesktopTower, + mdiDotsVertical, + mdiEmail, + mdiFileCloud, + mdiFormatListBulleted, + mdiHelp, + mdiMapMarker, + mdiMenu, + mdiNote, + mdiNoteText, + mdiPalette, + mdiPaperclip, + mdiPencil, + mdiPhone, + mdiPlaylistPlus, + mdiPlusCircle, + mdiPuzzle, + mdiServer, + mdiSync, + mdiTargetAccount, + mdiTextBoxSearch, + mdiThemeLightDark, + mdiTimelineText, + mdiTimetable, + mdiViewDashboardOutline, + mdiViewList, + mdiWeb +} from '@mdi/js' + +export default { + 'mdi-account-arrow-right': mdiAccountArrowRight, + 'mdi-account-cancel': mdiAccountCancel, + 'mdi-account-group': mdiAccountGroup, + 'mdi-arrow-collapse': mdiArrowCollapse, + 'mdi-arrow-collapse-left': mdiArrowCollapseLeft, + 'mdi-arrow-collapse-right': mdiArrowCollapseRight, + 'mdi-arrow-collapse-up': mdiArrowCollapseUp, + 'mdi-arrow-expand': mdiArrowExpand, + 'mdi-arrow-expand-down': mdiArrowExpandDown, + 'mdi-arrow-expand-right': mdiArrowExpandRight, + 'mdi-arrow-expand-left': mdiArrowExpandLeft, + 'mdi-arrow-left': mdiArrowLeft, + 'mdi-arrow-right': mdiArrowRight, + 'mdi-at': mdiAt, + 'mdi-book-account': mdiBookAccount, + 'mdi-cash-multiple': mdiCashMultiple, + 'mdi-check': mdiCheck, + 'mdi-clock-end': mdiClockEnd, + 'mdi-close': mdiClose, + 'mdi-delete': mdiDelete, + 'mdi-desktop-tower': mdiDesktopTower, + 'mdi-dots-vertical': mdiDotsVertical, + 'mdi-email': mdiEmail, + 'mdi-file-cloud': mdiFileCloud, + 'mdi-format-list-bulleted': mdiFormatListBulleted, + 'mdi-help': mdiHelp, + 'mdi-map-marker': mdiMapMarker, + 'mdi-menu': mdiMenu, + 'mdi-note': mdiNote, + 'mdi-note-text': mdiNoteText, + 'mdi-palette': mdiPalette, + 'mdi-paperclip': mdiPaperclip, + 'mdi-pencil': mdiPencil, + 'mdi-phone': mdiPhone, + 'mdi-playlist-plus': mdiPlaylistPlus, + 'mdi-plus-circle': mdiPlusCircle, + 'mdi-puzzle': mdiPuzzle, + 'mdi-server': mdiServer, + 'mdi-sync': mdiSync, + 'mdi-target-account': mdiTargetAccount, + 'mdi-text-box-search': mdiTextBoxSearch, + 'mdi-theme-light-dark': mdiThemeLightDark, + 'mdi-timeline-text': mdiTimelineText, + 'mdi-timetable': mdiTimetable, + 'mdi-view-dashboard-outlined': mdiViewDashboardOutline, + 'mdi-view-list': mdiViewList, + 'mdi-web': mdiWeb +} diff --git a/vuejs/webapp/vuejs/src/main.js b/vuejs/webapp/vuejs/src/main.js new file mode 100644 index 000000000..ff23d2f3e --- /dev/null +++ b/vuejs/webapp/vuejs/src/main.js @@ -0,0 +1,229 @@ +import Vue from 'vue' +import VueResource from 'vue-resource' +import VueRouter from 'vue-router' + +import VueWait from 'vue-wait' +import _ from 'lodash' +import VueTheMask from 'vue-the-mask' + +import App from './components/App' +import Portal from './components/Portal' +import VueForm from './components/VueForm' +import VueField from './components/VueField' +import VueDropDownField from './components/VueDropDownField' +import VueHiddenField from './components/VueHiddenField' +import VueRadioField from './components/VueRadioField' +import VueSubmitField from './components/VueSubmitField' +import VueTextFindField from './components/VueTextFindField' +import VueLabel from './components/VueLabel' +import VueOption from './components/VueOption' +import VueDisplayField from './components/VueDisplayField' +import VueTextField from './components/VueTextField' +import VueLookupField from './components/VueLookupField' +import VueTextAreaField from './components/VueTextAreaField' +import VueDateTimeField from './components/VueDateTimeField' +import VueError from './components/VueError' +import VueAsterisks from './components/VueAsterisks' +import VueTable from './components/VueTable' +import VueTr from './components/VueTr' +import VueSortField from './components/VueSortField' +import VueColumnPortlet from './components/VueColumnPortlet' +import VuePortlet from './components/VuePortlet' +import VueThead from './components/VueThead' +import VueListWrapper from './components/VueListWrapper' +import VueHeader from './components/VueHeader' +import VueHeaderRow from './components/VueHeaderRow' +import VueHeaderRowCell from './components/VueHeaderRowCell' +import VueFieldTitle from './components/VueFieldTitle' +import VueItemRow from './components/VueItemRow' +import VueItemRowCell from './components/VueItemRowCell' +import VueSingleWrapper from './components/VueSingleWrapper' +import VueFieldRow from './components/VueFieldRow' +import VueFieldRowTitleCell from './components/VueFieldRowTitleCell' +import VueFieldRowWidgetCell from './components/VueFieldRowWidgetCell' +import VueFieldGroup from './components/VueFieldGroup' +import VueHyperlinkField from './components/VueHyperlinkField' +import VueNextPrev from './components/VueNextPrev' +import VueScreenlet from './components/VueScreenlet' +import VueMenu from './components/VueMenu' +import VueMenuItem from './components/VueMenuItem' +import VueLink from './components/VueLink' +import VueContainer from './components/VueContainer' +import VueMessageList from './components/VueMessageList' +import VueLogin from './components/VueLogin' +import VueFormatEmptySpace from './components/VueFormatEmptySpace' +import VuePlatformSpecific from './components/VuePlatformSpecific' +import VueNavMenu from './components/VueNavMenu' +import VueNavMenuItem from './components/VueNavMenuItem' +import Screen from './components/Screen' +import VuePasswordField from './components/VuePasswordField' +import VueNavMenuItemInline from './components/VueNavMenuItemInline' +import VueHorizontalSeparator from './components/VueHorizontalSeparator' +import AppMenu from './components/AppMenu' +import AppWait from './components/AppWait' +import AppCpt from './components/AppCpt' +import VueColumn from './components/VueColumn' +import VueColumnContainer from './components/VueColumnContainer' +import SeleniumInfoPanel from './components/SeleniumInfoPanel' +import AppLog from './components/AppLog' +import AppDialog from './components/AppDialog' + +// Platform Specific +import ContactMech from './components/platformSpecific/ContactMech' +import store from './store' +import vuetify from '@/plugins/vuetify'; + +Vue.use(VueResource) +Vue.use(VueRouter) + +Vue.use(VueWait) +Vue.use(VueTheMask) + +Vue.component('portal', Portal) +Vue.component('screen', Screen) +Vue.component('vue-form', VueForm) +Vue.component('vue-field', VueField) +Vue.component('vue-drop-down-field', VueDropDownField) +Vue.component('vue-hidden-field', VueHiddenField) +Vue.component('vue-hyperlink-field', VueHyperlinkField) +Vue.component('vue-radio-field', VueRadioField) +Vue.component('vue-submit-field', VueSubmitField) +Vue.component('vue-text-find-field', VueTextFindField) +Vue.component('vue-label', VueLabel) +Vue.component('vue-option', VueOption) +Vue.component('vue-display-field', VueDisplayField) +Vue.component('vue-text-field', VueTextField) +Vue.component('vue-lookup-field', VueLookupField) +Vue.component('vue-text-area-field', VueTextAreaField) +Vue.component('vue-date-time-field', VueDateTimeField) +Vue.component('vue-error', VueError) +Vue.component('vue-asterisks', VueAsterisks) +Vue.component('vue-table', VueTable) +Vue.component('vue-tr', VueTr) +Vue.component('vue-sort-field', VueSortField) +Vue.component('vue-column-portlet', VueColumnPortlet) +Vue.component('vue-portlet', VuePortlet) +Vue.component('vue-thead', VueThead) +Vue.component('vue-list-wrapper', VueListWrapper) +Vue.component('vue-header', VueHeader) +Vue.component('vue-header-row', VueHeaderRow) +Vue.component('vue-header-row-cell', VueHeaderRowCell) +Vue.component('vue-field-title', VueFieldTitle) +Vue.component('vue-item-row', VueItemRow) +Vue.component('vue-item-row-cell', VueItemRowCell) +Vue.component('vue-single-wrapper', VueSingleWrapper) +Vue.component('vue-field-row', VueFieldRow) +Vue.component('vue-field-row-title-cell', VueFieldRowTitleCell) +Vue.component('vue-field-row-widget-cell', VueFieldRowWidgetCell) +Vue.component('vue-field-group', VueFieldGroup) +Vue.component('vue-next-prev', VueNextPrev) +Vue.component('vue-screenlet', VueScreenlet) +Vue.component('vue-menu', VueMenu) +Vue.component('vue-menu-item', VueMenuItem) +Vue.component('vue-link', VueLink) +Vue.component('vue-container', VueContainer) +Vue.component('vue-message-list', VueMessageList) +Vue.component( 'vue-login', VueLogin) +Vue.component('vue-format-empty-space', VueFormatEmptySpace) +Vue.component('vue-platform-specific', VuePlatformSpecific) +Vue.component('vue-nav-menu', VueNavMenu) +Vue.component('vue-nav-menu-item', VueNavMenuItem) +Vue.component('vue-password-field', VuePasswordField) +Vue.component('vue-nav-menu-item-inline', VueNavMenuItemInline) +Vue.component('vue-horizontal-separator', VueHorizontalSeparator) +Vue.component('app-menu', AppMenu) +Vue.component('app-wait', AppWait) +Vue.component('app-cpt', AppCpt) +Vue.component('vue-column', VueColumn) +Vue.component('vue-column-container', VueColumnContainer) +Vue.component('selenium-info-panel', SeleniumInfoPanel) +Vue.component('app-log', AppLog) +Vue.component('app-dialog', AppDialog) + +Vue.component('ContactMech', ContactMech) + +Object.defineProperty(Vue.prototype, '$_', {value: _}) + +const showDebug = false +Object.defineProperty(Vue.prototype, '$debug', {value: (process.env.NODE_ENV !== 'production') && showDebug}) + +Vue.mixin({ + methods: { + parseProps() { + if (this.$props && this.$props.props) { + let props = this.$props.props + let data = {} + if (props) { + this.props.attributes.map(attr => { + if (attr.value === 'false') { + data[attr.key] = false + } else if (attr.value === 'true') { + data[attr.key] = true + } else { + data[attr.key] = attr.value + } + }) + return data + } + } + }, + getNestedObject(nestedObject, pathArray) { + return pathArray.reduce((obj, key) => + (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObject) + }, + async asyncForEach(array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array) + } + }, + parseUrl(url, map) { + let parsedUrl = url + while (parsedUrl.includes('{')) { + let regexKey = /{(\w+)}/ + let regexReplace = /{\w+}/ + if (map[regexKey.exec(parsedUrl)[1]]) { + parsedUrl = parsedUrl.replace(regexReplace, map[regexKey.exec(parsedUrl)[1]]) + } else { + parsedUrl = parsedUrl.replace(regexReplace, `__${regexKey.exec(parsedUrl)[1]}_not_found__`) + this.$store.dispatch('backOfficeApi/addMessage', {messageContent: `${regexKey.exec(parsedUrl)[1]} not found for url ${url}`}) + } + } + return parsedUrl + }, + log(message) { + this.$store.dispatch('data/addLog', message) + } + } +}) + +const router = new VueRouter({ + mode: 'hash', + routes: [ + {path: '/portalPage/:portalPageId', props: true, component: Portal}, + {path: '/screen/:screenId', props: true, component: Screen}, + {path: '/screen/:screenId/:cover', props: true, component: Screen}, + ] +}) + +new Vue({ + el: '#app', + props: ['content'], + store: store, + router: router, + wait: new VueWait({ + useVuex: true + }), + vuetify, + css: { + extract: false, + loaderOptions: { + sass: { + data: `@import "~@./sass/main.scss"`, + }, + css: { + extract: false + } + }, + }, + render: h => h(App) +}) diff --git a/vuejs/webapp/vuejs/src/plugins/vuetify.js b/vuejs/webapp/vuejs/src/plugins/vuetify.js new file mode 100644 index 000000000..e057bcb46 --- /dev/null +++ b/vuejs/webapp/vuejs/src/plugins/vuetify.js @@ -0,0 +1,29 @@ +import Vue from 'vue'; +import Vuetify from 'vuetify/lib'; + +Vue.use(Vuetify); + +export default new Vuetify({ + icons: { + iconfont: 'mdiSvg', + }, + theme: { + dark: false, + themes: { + light: { + primary: '#41b883', + secondary: '#35495e', + accent: '#d52c3e', + success: '#64dd17', + error: '#f44336' + }, + dark: { + primary: '#41b883', + secondary: '#35495e', + accent: '#d52c3e', + success: '#64dd17', + error: '#f44336' + } + } + } +}); diff --git a/vuejs/webapp/vuejs/src/store/index.js b/vuejs/webapp/vuejs/src/store/index.js new file mode 100644 index 000000000..333b4e6dd --- /dev/null +++ b/vuejs/webapp/vuejs/src/store/index.js @@ -0,0 +1,21 @@ +import Vue from 'vue' +import Vuex from 'vuex' + +import login from './modules/login' +import ui from './modules/ui' +import data from './modules/data' +import form from './modules/form' +import backOfficeApi from './modules/backOfficeApi' + +Vue.use(Vuex) + +export default new Vuex.Store({ + modules: { + login, + ui, + data, + form, + backOfficeApi + }, + strict: process.env.NODE_ENV !== 'production' +}) diff --git a/vuejs/webapp/vuejs/src/store/modules/backOfficeApi.js b/vuejs/webapp/vuejs/src/store/modules/backOfficeApi.js new file mode 100644 index 000000000..cad19b561 --- /dev/null +++ b/vuejs/webapp/vuejs/src/store/modules/backOfficeApi.js @@ -0,0 +1,214 @@ +import Vuex from 'vuex' +import Vue from 'vue' +import constants from '../../js/constants' +import queryString from 'query-string' + +Vue.use(Vuex) + +const state = { + messageList: [], + currentApi: '' +} + +const mutations = { + ADD_MESSAGE(state, {messageContent, messageType}) { + Vue.set(state, 'messageList', [...state.messageList, {messageContent, messageType}]) + }, + DELETE_MESSAGE(state, {message}) { + state.messageList.splice(state.messageList.indexOf(message), 1) + }, + SET_CURRENT_API(state, api) { + Vue.set(state, 'currentApi', api) + } +} + +const getters = { + messageList: state => state.messageList, + currentApi: state => state.currentApi, + apiUrl: state => constants.hostUrl + state.currentApi +} + +const actions = { + doRequest({commit, dispatch, rootGetters}, {uri, mode, params, hideEventMessage, hideErrorMessage}) { + return new Promise((resolve, reject) => { + setTimeout(async () => { + let waitForLogin = false + do { + if (waitForLogin) { + await new Promise((res) => { + setTimeout(() => { + res() + }, 1000) + }) + } + let promise = null + switch (mode) { + case 'post': + promise = dispatch('doPost', {uri, params}) + break + case 'get': + promise = dispatch('doGet', {uri}) + break + case 'put': + promise = dispatch('doPut', {uri, params}) + break + case 'delete': + promise = dispatch('doDelete', {uri, params}) + break + default: + promise = dispatch('doPost', {uri, params}) + break + } + + let response = await promise.catch((error) => { + reject(error) + }) + + if (typeof response.body === 'string' && response.body.includes('login failed')) { + if (!waitForLogin) { + dispatch('ui/setDialogStatus', { + dialogId: 'loginDialog', + dialogStatus: true + }, {root: true}) + await dispatch('login/logout', {}, {root: true}) + waitForLogin = true + } + continue + } + if (response.body.hasOwnProperty('_ERROR_MESSAGE_')) { + if (!hideErrorMessage) { + commit('ADD_MESSAGE', {messageContent: response.body['_ERROR_MESSAGE_'], messageType: 'error'}) + } + reject(response) + } + if (response.body.hasOwnProperty('_ERROR_MESSAGE_LIST_')) { + if (!hideErrorMessage) { + for (let errorMessage of response.body['_ERROR_MESSAGE_LIST_']) { + commit('ADD_MESSAGE', {messageContent: errorMessage, messageType: 'error'}) + } + } + reject(response) + } + if (!hideEventMessage) { + if (response.body.hasOwnProperty('_EVENT_MESSAGE_')) { + commit('ADD_MESSAGE', {messageContent: response.body['_EVENT_MESSAGE_'], messageType: 'event'}) + } + if (response.body.hasOwnProperty('_EVENT_MESSAGE_LIST_')) { + for (let eventMessage of response.body['_EVENT_MESSAGE_LIST_']) { + commit('ADD_MESSAGE', {messageContent: eventMessage, messageType: 'event'}) + } + } + } + if (response.body.hasOwnProperty('viewEntities')) { + let entities = [] + let records = [] + Object.keys(response.body.viewEntities).forEach((key) => { + entities.push(dispatch('data/setEntity', { + entityName: key, + primaryKey: response.body.viewEntities[key].primaryKeys.join('-') + }, { + root: true + })) + }) + Promise.all(entities).then(all => { + all.forEach((entity => { + response.body.viewEntities[entity.entityName].list.forEach((record) => { + if (record.stId !== null) { + let data = { + entityName: entity.entityName, + primaryKey: record.stId, + data: record + } + records.push(dispatch('data/setEntityRow', data, {root: true})) + } + }) + })) + }) + Promise.all(records).then(() => { + resolve(response) + }) + } else { + resolve(response) + } + } while (!rootGetters['login/isLoggedIn']) + }, 0) + }) + }, + doPost(context, {uri, params}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + Vue.http.post(uri, + queryString.stringify({...params}), + {headers: {'Content-Type': 'application/x-www-form-urlencoded'}} + ).then( + response => { + resolve(response) + }, error => { + reject(error) + }) + }, 0) + }) + }, + doGet(context, {uri}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + Vue.http.get(uri, + queryString.stringify({}), + {headers: {'Content-Type': 'application/x-www-form-urlencoded'}} + ).then( + response => { + resolve(response) + }, error => { + reject(error) + }) + }, 0) + }) + }, + doPut(context, {uri}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + Vue.http.put(uri, + queryString.stringify({}), + {headers: {'Content-Type': 'application/x-www-form-urlencoded'}} + ).then( + response => { + resolve(response) + }, error => { + reject(error) + }) + }, 0) + }) + }, + doDelete(context, {uri}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + Vue.http.delete(uri, + queryString.stringify({}), + {headers: {'Content-Type': 'application/x-www-form-urlencoded'}} + ).then( + response => { + resolve(response) + }, error => { + reject(error) + }) + }, 0) + }) + }, + addMessage({commit}, {messageContent, messageType}) { + commit('ADD_MESSAGE', {messageContent, messageType}) + }, + deleteMessage({commit}, {message}) { + commit('DELETE_MESSAGE', {message}) + }, + setApi({commit}, api) { + commit('SET_CURRENT_API', api) + } +} + +export default { + namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/vuejs/webapp/vuejs/src/store/modules/data.js b/vuejs/webapp/vuejs/src/store/modules/data.js new file mode 100644 index 000000000..d2dcc5434 --- /dev/null +++ b/vuejs/webapp/vuejs/src/store/modules/data.js @@ -0,0 +1,133 @@ +import Vuex from 'vuex' +import Vue from 'vue' + +Vue.use(Vuex) + +const state = { + entities: {}, + watchers: {}, + logs: [] +} + +const mutations = { + SET_ENTITY: (state, data) => { + Vue.set(state.entities, data.entityName, { + list: {}, + primaryKey: data.primaryKey + }) + }, + SET_ENTITY_ROW: (state, data) => { + if (state.entities[data.entityName] === undefined) { + return 'Entities doesn\'t exist' + } + if (!state.entities[data.entityName].list[data.primaryKey]) { + Vue.set(state.entities[data.entityName].list, data.primaryKey, {}) + } + Object.keys(data.data).forEach(key => { + Vue.set(state.entities[data.entityName].list[data.primaryKey], key, data.data[key]) + }) + }, + SET_WATCHER: (state, {watcherName, params}) => { + Vue.set(state.watchers, watcherName, {...params}) + }, + SET_WATCHER_ATTRIBUTES: (state, {watcherName, params}) => { + Vue.set(state.watchers, watcherName, {...state.watchers[watcherName], ...params}) + }, + ADD_LOG: (state, log) => { + state.logs.push(log) + }, + CLEAR_LOGS: (state) => { + Vue.set(state, 'logs', []) + } +} + +const getters = { + entities: state => state.entities, + entity: state => entityName => { + return state.entities[entityName] + }, + entityRow: state => ({entityName, id}) => { + return state.entities[entityName].list.find(row => row.stId === id) + }, + entityRowAttribute(state) { + return function ({entityName, id, attribute}) { + if (state.entities[entityName] === undefined || + state.entities[entityName].list[id] === undefined || + state.entities[entityName].list[id][attribute] === undefined + ) { + return '' + } else { + return state.entities[entityName].list[id][attribute] + } + } + }, + watcher(state) { + return function (watcherName) { + return state.watchers[watcherName] + } + }, + watchers(state) { + return state.watchers + }, + watchersList(state) { + return Object.keys(state.watchers) + }, + logs(state) { + return state.logs + } +} + +const actions = { + setEntity({commit}, data) { + return new Promise((resolve) => { + setTimeout(() => { + if (!state.entities[data.entityName]) { + commit('SET_ENTITY', data) + } + resolve(data) + }, 0) + }) + }, + setEntityRow({commit}, data) { + return new Promise((resolve) => { + setTimeout(() => { + commit('SET_ENTITY_ROW', data) + resolve(data) + }, 0) + }) + }, + setWatcher({commit, getters}, {watcherName, params}) { + commit('SET_WATCHER', {watcherName, params}) + getters['watchersList'].forEach((watcher) => { + if (watcher.split('-').length > 1 && watcher.split('-').includes(watcherName)) { + let merged = {} + for (let component of watcher.split('-')) { + merged = {...getters['watchers'][component], ...merged} + } + commit('SET_WATCHER', {watcherName: watcher, params: merged}) + } + }) + }, + initializeWatcher({commit, getters}, watcherName) { + if (!getters['watchersList'].includes(watcherName)) { + commit('SET_WATCHER', {watcherName: watcherName, params: {}}) + } + }, + setWatcherAttributes({commit}, data) { + commit('SET_WATCHER_ATTRIBUTES', data) + }, + addLog({commit}, log) { + commit('ADD_LOG', log) + }, + clearLogs({commit}) { + commit('CLEAR_LOGS') + } +} + +export default { + namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/vuejs/webapp/vuejs/src/store/modules/form.js b/vuejs/webapp/vuejs/src/store/modules/form.js new file mode 100644 index 000000000..ce660132a --- /dev/null +++ b/vuejs/webapp/vuejs/src/store/modules/form.js @@ -0,0 +1,61 @@ +import Vuex from 'vuex' +import Vue from 'vue' + +Vue.use(Vuex) + +const state = { + forms: {}, + formValidate: {} +} + +const mutations = { + ADD_FORM: (state, formName) => { + Vue.set(state.forms, formName, {}) + }, + SET_FIELD_TO_FORM: (state, data) => { + Vue.set(state.forms, data.formId, {...state.forms[data.formId], [data.key]: data.value}) + }, + ADD_FORM_VALIDATE: (state, {formName, validate}) => { + Vue.set(state.formValidate, formName, validate) + } +} + +const getters = { + forms: state => state.forms, + form: state => { + return function (formId) { + return state.forms[formId] + } + }, + fieldInForm: state => (data) => { + return state.forms[data.formId][data.key] + }, + formValidate: state => { + return function (formName) { + return state.formValidate[formName] + } + } +} + +const actions = { + addForm({commit}, formName) { + commit('ADD_FORM', formName) + }, + setFieldToForm({commit, state}, data) { + if (!state.forms[data.formId]) { + commit('ADD_FORM', data.formId) + } + commit('SET_FIELD_TO_FORM', data) + }, + addFormValidate({commit}, {formName, validate}) { + commit('ADD_FORM_VALIDATE', {formName, validate}) + } +} + +export default { + namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/vuejs/webapp/vuejs/src/store/modules/login.js b/vuejs/webapp/vuejs/src/store/modules/login.js new file mode 100644 index 000000000..871076362 --- /dev/null +++ b/vuejs/webapp/vuejs/src/store/modules/login.js @@ -0,0 +1,144 @@ +import Vuex from 'vuex' +import Vue from 'vue' +import queryString from 'query-string' +import constants from './../../js/constants' + +Vue.use(Vuex) + +const state = { + credentials: { + username: '', + token: '', + rememberMe: false, + partyId: '' + }, + isLoggedIn: false, + pending: true +} + +const mutations = { + CHECK_FAILURE: (state) => { + state.isLoggedIn = false + state.pending = false + }, + CHECK_SUCCESS: (state) => { + state.isLoggedIn = true + state.pending = false + }, + LOGIN_FAILURE: (state) => { + state.isLoggedIn = false + state.pending = false + }, + LOGIN_SUCCESS: (state, params) => { + state.isLoggedIn = true + state.credentials.username = params.username + state.pending = false + }, + LOGOUT: (state) => { + state.credentials.token = '' + state.credentials.username = '' + state.credentials.partyId = '' + state.isLoggedIn = false + state.pending = false + }, + START_PENDING: (state) => { + state.pending = true + } +} + +const getters = { + isLoggedIn: state => state.isLoggedIn, + pending: (state) => () => { + return state.pending + } +} + +const actions = { + login({commit, dispatch, rootGetters}, credentials) { + return new Promise((resolve, reject) => { + setTimeout(() => { + commit('START_PENDING') + Vue.http.post( + constants.hostUrl + rootGetters['backOfficeApi/currentApi'] + constants.login.path, + queryString.stringify({ + JavaScriptEnabled: 'Y', + USERNAME: credentials.username, + PASSWORD: credentials.password + }), + {headers: {'Content-Type': 'application/x-www-form-urlencoded'}} + ).then(response => { + if (!response.body._ERROR_MESSAGE_ && !response.body._ERROR_MESSAGES_LIST_) { + dispatch('backOfficeApi/addMessage', {messageContent: rootGetters['ui/uiLabels'].loginSuccessMessage, messageType: 'event'}, {root: true}) + commit('LOGIN_SUCCESS', credentials) + resolve() + } else { + if (response.body._ERROR_MESSAGE_) { + dispatch('backOfficeApi/addMessage', {messageContent: response.body._ERROR_MESSAGE_, messageType: 'error'}, {root: true}) + } + if (response.body._ERROR_MESSAGES_LIST_) { + for (let error in response.body._ERROR_MESSAGE_LIST_) { + dispatch('backOfficeApi/addMessage', {messageContent: error, messageType: 'error'}, {root: true}) + } + } + commit('LOGIN_FAILURE') + reject(response) + } + }, error => { + if (error.body._ERROR_MESSAGE_) { + dispatch('backOfficeApi/addMessage', {messageContent: error.body._ERROR_MESSAGE_, messageType: 'error'}, {root: true}) + } + if (error.body._ERROR_MESSAGES_LIST_) { + for (let error in error.body._ERROR_MESSAGE_LIST_) { + dispatch('backOfficeApi/addMessage', {messageContent: error, messageType: 'error'}, {root: true}) + } + } + commit('LOGIN_FAILURE') + reject(error) + }) + }, 0) + }) + }, + logout({commit}) { + return new Promise((resolve) => { + setTimeout(() => { + commit('START_PENDING') + localStorage.removeItem('token') + commit('LOGOUT') + resolve() + }, 0) + }) + }, + check({commit, rootGetters}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + commit('START_PENDING') + Vue.http.post( + constants.hostUrl + rootGetters['backOfficeApi/currentApi'] + constants.ajaxCheckLogin.path, + queryString.stringify({ + }), + {headers: {'Content-Type': 'application/x-www-form-urlencoded'}}).then(response => { + if (response.body.includes('login successful') + && !response.body._ERROR_MESSAGE_ + && !response.body._ERROR_MESSAGES_LIST_) { + commit('CHECK_SUCCESS') + resolve() + } else { + commit('CHECK_FAILURE') + reject() + } + }, () => { + commit('CHECK_FAILURE') + reject() + }) + }, 0) + }) + } +} + +export default { + namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/vuejs/webapp/vuejs/src/store/modules/ui.js b/vuejs/webapp/vuejs/src/store/modules/ui.js new file mode 100644 index 000000000..b85385813 --- /dev/null +++ b/vuejs/webapp/vuejs/src/store/modules/ui.js @@ -0,0 +1,342 @@ +import Vuex from 'vuex' +import Vue from 'vue' +import queryString from 'query-string' +import constants from './../../js/constants' + +Vue.use(Vuex) + +const state = { + currentPortalPage: '', + currentPortalPageParams: {}, + currentPortalPageDetail: {}, + portalPages: {}, + portlets: {}, + portletTarget: {}, + containers: {}, + areas: {}, + watchers: {}, + updateCpt: 0, + collapsibleStatus: {}, + dialogStatus: {}, + uiLabels: {}, + seleniumInfoPanels: [], + locale: {}, + logDrawerState: false, + areasRefresh: {}, + watchersRefresh: {} +} + +const mutations = { + SET_CURRENT_PORTAL_PAGE: (state, portalPageId) => { + Vue.set(state, 'currentPortalPage', portalPageId) + }, + SET_PORTAL_PAGE: (state, {portalPageId, portalPage}) => { + Vue.set(state.portalPages, portalPageId, portalPage) + Vue.set(state, 'currentPortalPageDetail', portalPage) + }, + REMOVE_PORTAL_PAGE: (state, id) => { + state.portalPages.slice(id) + }, + SET_CURRENT_PORTAL_PAGE_PARAMS: (state, params) => { + Vue.set(state, 'currentPortalPageParams', params) + }, + SET_PORTLET: (state, {portletId, data}) => { + Vue.set(state.portlets, portletId, data) + }, + SET_PORTLET_TARGET: (state, {portletId, target}) => { + Vue.set(state.portletTarget, portletId, target) + }, + SET_CONTAINER: (state, {containerName, content}) => { + Vue.set(state.containers, containerName, content) + }, + SET_AREA: (state, {areaId, areaContent}) => { + Vue.set(state.areas, areaId, areaContent) + }, + DELETE_AREA: (state, {areaId}) => { + Vue.delete(state.areas, areaId) + }, + INCREMENT_UPDATE_CPT: (state) => { + Vue.set(state, 'updateCpt', state.updateCpt + 1) + }, + SET_COLLAPSIBLE_STATUS: (state, {areaId, areaTarget}) => { + Vue.set(state.collapsibleStatus, areaId, areaTarget) + }, + SET_DIALOG_STATUS: (state, {dialogId, dialogStatus}) => { + Vue.set(state.dialogStatus, dialogId, dialogStatus) + }, + SET_UI_LABELS: (state, uiLabels) => { + Vue.set(state, 'uiLabels', uiLabels) + }, + ADD_SELENIUM_INFO_PANEL(state, panel) { + Vue.set(state, 'seleniumInfoPanels', [...state.seleniumInfoPanels, panel]) + }, + DELETE_SELENIUM_INFO_PANEL(state, panel) { + state.seleniumInfoPanels.splice(state.seleniumInfoPanels.indexOf(panel), 1) + }, + SET_LOCALE(state, locale) { + Vue.set(state, 'locale', locale) + }, + TOGGLE_LOG_DRAWER(state) { + Vue.set(state, 'logDrawerState', !state.logDrawerState) + }, + SET_AREA_REFRESH(state, areaId) { + Vue.set(state.areasRefresh, areaId, 0) + }, + REFRESH_AREA(state, areaId) { + Vue.set(state.areasRefresh, areaId, state.areasRefresh[areaId] + 1) + }, + SET_WATCHER_REFRESH(state, watcher) { + Vue.set(state.watchersRefresh, watcher, 0) + }, + REFRESH_WATCHER(state, watcher) { + Vue.set(state.watchersRefresh, watcher, state.watchersRefresh[watcher] + 1) + }, +} + +const getters = { + currentPortalPage: state => state.currentPortalPage, + currentPortalPageDetail: state => state.currentPortalPageDetail, + currentPortalPageParams: state => state.currentPortalPageParams, + portalPage: state => (id) => { + return state.portalPages[id] + }, + portalPages: state => state.portalPages, + column: state => ({portalPageId, columnSeqId}) => { + return state.portalPages[portalPageId].listColumnPortlet.find(col => col.columnSeqId === columnSeqId) + }, + portlet(state) { + return (id) => { + return state.portlets[id] + } + }, + portlets: state => state.portlets, + portletTarget(state) { + return function (id) { + return state.portletTarget.hasOwnProperty(id) ? state.portletTarget[id] : null + } + }, + container(state) { + return function (containerName) { + return state.containers.hasOwnProperty(containerName) ? state.containers[containerName] : null + } + }, + area(state) { + return function (areaId) { + return state.areas.hasOwnProperty(areaId) ? state.areas[areaId] : null + } + }, + watcher(state) { + return function (watcherName) { + return state.watchers.hasOwnProperty(watcherName) ? state.watchers[watcherName] : null + } + }, + updateCpt: state => state.updateCpt, + collapsibleStatus(state) { + return function (areaId) { + return state.collapsibleStatus.hasOwnProperty(areaId) ? state.collapsibleStatus[areaId] : false + } + }, + dialogStatus(state) { + return function (dialogId) { + return state.dialogStatus.hasOwnProperty(dialogId) ? state.dialogStatus[dialogId] : false + } + }, + dialogs: state => state.dialogStatus, + uiLabels: state => state.uiLabels, + uiLabel(state) { + return function (uiLabel) { + return state.uiLabels.hasOwnProperty(uiLabel) ? state.uiLabels[uiLabel] : uiLabel + } + }, + seleniumInfoPanels: state => state.seleniumInfoPanels, + locale: state => state.locale, + logDrawerState: state => state.logDrawerState, + areasRefresh: state => state.areasRefresh, + areaRefresh(state) { + return function (areaId) { + return state.areasRefresh.hasOwnProperty(areaId) ? state.areasRefresh[areaId] : 0 + } + }, + watchersRefresh: state => state.watchersRefresh, + watcherRefresh(state) { + return function (watcher) { + return state.watchersRefresh.hasOwnProperty(watcher) ? state.watchersRefresh[watcher] : 0 + } + } +} + +const actions = { + setPortalPage({commit}, {portalPageId, portalPage}) { + commit('SET_PORTAL_PAGE', {portalPageId, portalPage}) + commit('SET_CURRENT_PORTAL_PAGE', portalPageId) + }, + setPortlet({commit, getters}, {portalPortletId, portletSeqId, params = {}}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + Vue.http.post(getters['backOfficeApi/apiUrl'] + getters['backOfficeApi/currentApi'] + constants.showPortlet.path, + queryString.stringify({ + portalPortletId: portalPortletId, + ...params + }), + {headers: {'Content-Type': 'application/x-www-form-urlencoded'}} + ).then(response => { + commit('SET_PORTLET', {portletId: portalPortletId + '-' + portletSeqId, data: response.body}) + resolve(portalPortletId) + }, () => { + reject() + }) + }, 0) + }) + }, + setPortletTarget({commit}, {portletId, target}) { + commit('SET_PORTLET_TARGET', {portletId, target}) + }, + setContainer({commit, getters}, {containerName, containerTarget, params = {}}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + Vue.http.post(getters['backOfficeApi/apiUrl'] + getters['backOfficeApi/currentApi'] + constants.showPortlet.path, + queryString.stringify({ + portalPortletId: containerTarget, + ...params + }), + {headers: {'Content-Type': 'application/x-www-form-urlencoded'}} + ).then(response => { + commit('SET_CONTAINER', {containerName, content: response.body}) + resolve(containerName) + }, () => { + reject() + }) + }, 0) + }) + }, + setArea({commit, dispatch}, {areaId, targetUrl, params = {}, mode = 'post'}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + dispatch('wait/start', areaId, {root: true}) + dispatch('incrementUpdateCpt') + dispatch( + 'backOfficeApi/doRequest', + {uri: constants.hostUrl + targetUrl.replace('amp;', ''), params, mode}, + {root: true} + ).then(response => { + if (response.body.hasOwnProperty('_ERROR_MESSAGE_')) { + dispatch('addErrorMessage', {errorMessage: response.body['_ERROR_MESSAGE_']}) + setTimeout(() => { + dispatch('wait/end', areaId, {root: true}) + }, 0) + reject() + } + if (response.body.hasOwnProperty('_ERROR_MESSAGE_LIST_')) { + for (let errorMessage of response.body['_ERROR_MESSAGE_LIST_']) { + dispatch('addErrorMessage', {errorMessage}) + } + setTimeout(() => { + dispatch('wait/end', areaId, {root: true}) + }, 0) + reject() + } + commit('SET_AREA', {areaId: areaId, areaContent: response.body}) + setTimeout(() => { + dispatch('wait/end', areaId, {root: true}) + }, 0) + resolve(areaId) + }, error => { + setTimeout(() => { + dispatch('wait/end', areaId, {root: true}) + }, 0) + reject(error) + }) + }, 0) + }) + }, + deleteArea({commit}, {areaId}) { + commit('DELETE_AREA', {areaId}) + }, + incrementUpdateCpt({commit}) { + commit('INCREMENT_UPDATE_CPT') + }, + initialize({dispatch}, location) { + let pathname = location.pathname + let search = location.search + let params = {portalPageId: this._vm.$route.params.portalPageId} + search.substr(1).split('&').forEach(param => { + let tmp = param.split('=') + params[tmp[0]] = tmp[1] + }) + let api = pathname.substring(0, pathname.indexOf('/', 1)) + '/control' + dispatch('loadPortalPageDetail', {api: api, params: params}) + }, + loadPortalPageDetail({commit, dispatch}, {api, params}) { + dispatch('backOfficeApi/setApi', api, {root: true}) + dispatch('backOfficeApi/doRequest', {uri: constants.hostUrl + api + constants.portalPageDetail.path, mode: 'post', params}, {root: true}).then(response => { + let portalPage = response.body + commit('SET_CURRENT_PORTAL_PAGE_PARAMS', params) + commit('SET_PORTAL_PAGE', {portalPageId: params.portalPageId, portalPage}) + commit('SET_CURRENT_PORTAL_PAGE', params.portalPageId) + }) + }, + setCollapsibleStatus({commit}, {areaId, areaTarget}) { + commit('SET_COLLAPSIBLE_STATUS', {areaId, areaTarget}) + }, + setDialogStatus({commit}, {dialogId, dialogStatus}) { + commit('SET_DIALOG_STATUS', {dialogId, dialogStatus}) + }, + closeAllDialogs({commit, getters}) { + for (let dialogId of Object.keys(getters.dialogs)) { + commit('SET_DIALOG_STATUS', {dialogId, dialogStatus: false}) + } + }, + setUiLabels({commit, dispatch}, api) { + dispatch('backOfficeApi/doRequest', {uri: constants.hostUrl + api + constants.getCommonUiLabel, mode: 'post'}, {root: true}) + .then(response => { + commit('SET_UI_LABELS', response.body.commonUiLabels) + }) + }, + addSeleniumInfoPanel({commit}, {panelMessage, panelTimeout, panelTitle, panelColor}) { + commit('ADD_SELENIUM_INFO_PANEL', {panelMessage, panelTimeout, panelTitle, panelColor}) + }, + deleteSeleniumInfoPanel({commit}, infoPanel) { + commit('DELETE_SELENIUM_INFO_PANEL', infoPanel) + }, + setLocale({commit}, locale) { + commit('SET_LOCALE', locale) + }, + toggleLogDrawer({commit}) { + commit('TOGGLE_LOG_DRAWER') + }, + setAreaRefresh({commit, getters}, areaId) { + if (!getters['areasRefresh'][areaId]) { + commit('SET_AREA_REFRESH', areaId) + } + }, + refreshArea({commit, getters}, areaId) { + if (!getters['areasRefresh'].hasOwnProperty(areaId)) { + commit('SET_AREA_REFRESH', areaId) + } + commit('REFRESH_AREA', areaId) + }, + setWatcherRefresh({commit, getters}, watcherName) { + if (!getters['watchersRefresh'][watcherName]) { + commit('SET_AREA_REFRESH', watcherName) + } + }, + refreshWatcher({commit, getters}, watcherName) { + if (!getters['watchersRefresh'].hasOwnProperty(watcherName)) { + commit('SET_WATCHER_REFRESH', watcherName) + } + commit('REFRESH_WATCHER', watcherName) + getters['watchersRefresh'].forEach((watcher) => { + if (watcher.split('-').length > 1 && watcher.split('-').includes(watcherName)) { + commit('REFRESH_WATCHER', watcher) + } + }) + } +} + +export default { + namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/vuejs/webapp/vuejs/vue.config.js b/vuejs/webapp/vuejs/vue.config.js new file mode 100644 index 000000000..b6edfa2ba --- /dev/null +++ b/vuejs/webapp/vuejs/vue.config.js @@ -0,0 +1,18 @@ +module.exports = { + css: { + extract: false, + }, + configureWebpack: { + output: { + libraryExport: 'default' + } + }, + chainWebpack: (config) => { + config.optimization.minimizer('terser').tap((args) => { + args[0].terserOptions.compress.drop_console = true + args[0].terserOptions.compress.pure_funcs = ['console.log'] + return args + }) + }, + filenameHashing: false +} diff --git a/vuejs/webcommon/vuejs-controller.xml b/vuejs/webcommon/vuejs-controller.xml new file mode 100644 index 000000000..8f0066e87 --- /dev/null +++ b/vuejs/webcommon/vuejs-controller.xml @@ -0,0 +1,105 @@ + + + + + Common controller entries for all Vuejs App Components + + + + + + + + + + + + + + + + Verify a user is logged in. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vuejs/widget/CommonScreens.xml b/vuejs/widget/CommonScreens.xml new file mode 100644 index 000000000..35fc0b17c --- /dev/null +++ b/vuejs/widget/CommonScreens.xml @@ -0,0 +1,213 @@ + + + + + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+ +
+ + + + + + + + <#if layoutSettings.javaScripts?has_content> +--> + <#--layoutSettings.javaScripts is a list of java scripts. --> + <#-- use a Set to make sure each javascript is declared only once, but iterate the list to maintain the correct order --> +<#-- + <#assign javaScriptsSet = Static["org.apache.ofbiz.base.util.UtilMisc"].toSet(layoutSettings.javaScripts)/> + <#list layoutSettings.javaScripts as javaScript> + <#if javaScriptsSet.contains(javaScript)> + <#assign nothing = javaScriptsSet.remove(javaScript)/> + + + + +--> + + + + <#if layoutSettings.styleSheets?has_content> + <#--layoutSettings.styleSheets is a list of style sheets. So, you can have a user-specified "main" style sheet, AND a component style sheet.--> + <#list layoutSettings.styleSheets as styleSheet> + + + + <#if layoutSettings.VT_STYLESHEET?has_content> + <#list layoutSettings.VT_STYLESHEET as styleSheet> + + + + <#if layoutSettings.rtlStyleSheets?has_content && "rtl" == langDir> + <#--layoutSettings.rtlStyleSheets is a list of rtl style sheets.--> + <#list layoutSettings.rtlStyleSheets as styleSheet> + + + + <#if layoutSettings.VT_RTL_STYLESHEET?has_content && "rtl" == langDir> + <#list layoutSettings.VT_RTL_STYLESHEET as styleSheet> + + + + <#if layoutSettings.VT_EXTRA_HEAD?has_content> + <#list layoutSettings.VT_EXTRA_HEAD as extraHead> + ${extraHead} + + + <#if lastParameters??><#assign parametersURL = "&" + lastParameters> + <#if layoutSettings.WEB_ANALYTICS?has_content> + + + +<#if layoutSettings.headerImageLinkUrl??> + <#assign logoLinkURL = "${layoutSettings.headerImageLinkUrl}"> +<#else> + <#assign logoLinkURL = "${layoutSettings.commonHeaderImageLinkUrl}"> + +<#assign organizationLogoLinkURL = "${layoutSettings.organizationLogoLinkUrl!}"> + + <#include "component://common-theme/template/ImpersonateBanner.ftl"/> + +
+ +
+ +
+ <#--
--> diff --git a/themefrontjs/webapp/thfrontjs/WEB-INF/web.xml b/themefrontjs/webapp/thfrontjs/WEB-INF/web.xml new file mode 100644 index 000000000..8260672b0 --- /dev/null +++ b/themefrontjs/webapp/thfrontjs/WEB-INF/web.xml @@ -0,0 +1,29 @@ + + + + + Apache OFBiz - Flat Grey Visual Theme for Front Js + Flat Grey Fjs Visual Theme + + + index.jsp + main.jsp + + diff --git a/themefrontjs/webapp/thfrontjs/faded_background.png b/themefrontjs/webapp/thfrontjs/faded_background.png new file mode 100644 index 0000000000000000000000000000000000000000..5ca3f75758eb23045753cfef1983576ca51dfd2f GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z;_sg8IR|$NC65;mbgZg1m~xflqVLYG6W=M=9TFAxrQi|8S9zq8C`#u=?qjf z!_&nvB*Xdbbx*zq10Ls#MG5crr&=&bpJrOrekC>LsQt!M+pgU@Z~SPt)XV}mlS2Qi zEK*WGm0TBpHPz}+2C{5aNeT6GrVHw**W>q)g VtM}?BMFQQ(;OXk;vd$@?2>_P0dKmx! literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/header_bg.gif b/themefrontjs/webapp/thfrontjs/header_bg.gif new file mode 100644 index 0000000000000000000000000000000000000000..3913cfdfd745e6e2d55316cf8d9858eb0f719afc GIT binary patch literal 1188 zcmV;V1Y7$@Nk%w1Vc!5F0K@*M3&+uPgq_4Uoo&F}E<;Naok-rniy z=;-I@=ji9&-rnct=HTDp=H}+;>FM9#-{Iik_xAVh?(X^c`0wuS_xSkl@bK~R@$>WZ z*x1{z@TtQEEsi`J`l%k2uYUSwcQE}PHjw0g~MyWjA*d`_?1 z@A$la&+q&HfPsR8goR`rh>41ejE#vXd%+1cv(9zP<%MI4o*xB0K+}+;a z;Njxq(BTJr4xw7TUmmz50 z%(=7Y&!9t#9!C>oFt6t5zG=tZ#7sOs5TQ-B*xNF0vO?!9j*|~537Vi6YaO1pn z_eQ>Zd2-&cjRT(^9lG%6%!gCAt^2og+t;m!=We~b^xVD6gMY0+L4@}3-NP3kpT2!$ zsGxxn0vIBRC|+oxg$~Xqz>6@(DC3MY)@b96IOeG1jy(40NG7S| zl1w(~`m}aW!rkr-_ z>8GHED(a>ilxpg!sHUpws;su^>Z`EED(kGY)@tjmuoj@}uDtf@>#x8DE9|hu7HjOW z$R?}ovdk7s!L!gtEA6z@R%`A5wb*8>?Y7)@>+QGThO4aw7GQt@x#Ut%F1qBNyDqxw zvMX=9>CS5}y6)0@@4fk&yYIX6<{L1(0t<}q!26zS@W1iK`)|MtKMe1_090(T1s7Ya zam5&K3;? zz4ixT!=3gAaLX42{%{SP2{|&g^eV;A3*mNJR_|}AbZFl03M=tr~lvi%~ z<(Ox#`R1H=?)m4Shc5ad=%kl!`st{ruKMb%x9 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/header_close_button.png b/themefrontjs/webapp/thfrontjs/header_close_button.png new file mode 100644 index 0000000000000000000000000000000000000000..dbbc6c342d073766e2f36fe57a03058dcfed6db7 GIT binary patch literal 3352 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 EAHN$8j{pDw literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/app-bar.png b/themefrontjs/webapp/thfrontjs/images/app-bar.png new file mode 100644 index 0000000000000000000000000000000000000000..fc618dcdbeaee5ecf367a80dd9fcf22deadcf1b7 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^Ahr<)6OepWc~=WaNtU=qlmzFem6RtIr7}3Cl%q1v1*sObpflT5~3C literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/big-fade.png b/themefrontjs/webapp/thfrontjs/images/big-fade.png new file mode 100644 index 0000000000000000000000000000000000000000..96e897dbc408b57b35c4069200a02c108b1336d8 GIT binary patch literal 646 zcmeAS@N?(olHy`uVBq!ia0vp^ZVU{J=^Si8mh?*d#XyR)z$3Dlfq`2Xgc%uT&5-~K zDkOUZ`7$u%sWUJXuV-NR?!ds%*2ln*`<8*>)l&ur!xIb)5~-=`FHSQsF!6Z0IEGZ* zdUInVZ-W7ktE2UX|Hl&8I5c%!1YiE+SpHn(I0GC9+Di<22HH|;byW37Bhnwjm) zb6%D%MrJl134??K4UEjkfW&9{9{c#CAkO2}K;b=xU5v>fGO#D~U1(3*;55s%nG9 zNEqCc09yID7ia{K84NYZ(=cJqq@)8NgOF?oGJU|#f!YpqAJlf3DTWB=13i=s_8Q2K zyYmzRe6k}$+1tt`o^NUJPx; sCrC9jTnjTBk;Wl;3(2i#;;PvgLNo59&1%@}4NOA}p00i_>zopr0C%{?;s5{u literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/button.png b/themefrontjs/webapp/thfrontjs/images/button.png new file mode 100644 index 0000000000000000000000000000000000000000..c4651629c96a3c01a537c8d230823d1b00a3427a GIT binary patch literal 85 zcmeAS@N?(olHy`uVBq!ia0vp^k|4|hBp4nx-P#YNWISCQLp07O|2f~lyy3yLpYCn* h4(8k{_k1T_os-(kd<<%J=^`5SNF6*2UngF?48YKV# literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/cal.gif b/themefrontjs/webapp/thfrontjs/images/cal.gif new file mode 100644 index 0000000000000000000000000000000000000000..8526cf5d19a915aa8073cf344873c4505491970d GIT binary patch literal 127 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 J&C;8B`hRtkWS9T| literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/logo.png b/themefrontjs/webapp/thfrontjs/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8dd26447a6133cb71ecd1ca4b3e10db3b51a7374 GIT binary patch literal 2774 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/main-nav.gif b/themefrontjs/webapp/thfrontjs/images/main-nav.gif new file mode 100644 index 0000000000000000000000000000000000000000..c2849abc5e3e555534ba19be4b5a49dd9ff592f2 GIT binary patch literal 4495 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/masthead.gif b/themefrontjs/webapp/thfrontjs/images/masthead.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6779e366b5f3c5495cecfcc6abf3326e6d3e68c GIT binary patch literal 1523 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

pYaX?d{efXvQOb;O~g#NM-tsy)W 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/FindParties.png b/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/FindParties.png new file mode 100755 index 0000000000000000000000000000000000000000..1dcd7b3597d091924ed2cc547658c3d420822df2 GIT binary patch literal 29927 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/PartyIdentification.png b/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/PartyIdentification.png new file mode 100755 index 0000000000000000000000000000000000000000..218b683b43d365c1fba10719e0a1a6cfbf30b7d0 GIT binary patch literal 13994 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 JE~M@C{{S5-lVSh> literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/PartyPaymentMethods.png b/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/PartyPaymentMethods.png new file mode 100755 index 0000000000000000000000000000000000000000..aa75753d1aed2a9d19bd632de425616c0b8c4eac GIT binary patch literal 29367 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 O;*O%mtr7*Z=l=uvEaAWa literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/PartyRoles.png b/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/PartyRoles.png new file mode 100755 index 0000000000000000000000000000000000000000..15a9f37bca782dcbf505b4ecb60de2b5c64bacf1 GIT binary patch literal 27855 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 JL)R|ge*mU9xP1Ts literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/SelectParty.png b/themefrontjs/webapp/thfrontjs/images/partyfjsportlets/SelectParty.png new file mode 100755 index 0000000000000000000000000000000000000000..b697261a0af2e4f2d46dc0a3cb2078be4ab5980e GIT binary patch literal 5556 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/tile.gif b/themefrontjs/webapp/thfrontjs/images/tile.gif new file mode 100644 index 0000000000000000000000000000000000000000..6484683f1a5b9116a8d3b0ed016b988f9e4462e3 GIT binary patch literal 25807 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 XzoJ{NVo9&!yH&+sNDL4O1la!{p=K|s literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-anim_basic_16x16.gif b/themefrontjs/webapp/thfrontjs/images/ui-anim_basic_16x16.gif new file mode 100644 index 0000000000000000000000000000000000000000..085ccaecaf5fa5c34bc14cd2c2ed5cbbd8e25dcb GIT binary patch literal 1553 zcma)+TTl~c6vwlh>nb99Af5rT)t{mCEg5urg=A(g z{C|6SPb~9Xage|wB`SrZk2FOMYM!buln2sX?5Y+T78iB(Zu9cS7|LZyZ++}u$^oi1 z_j@S}bW9OzU2R+RMy&~OT>X-oZ98$jq#ogNfJ!BM-42wHGZk*6s2KD}U*IA%epmxb zm}|6BK9YoIF;*xSL!+z@<64lB7->LTW2Vi4ostCA(z&2XniwNIv}fFo-`MbG;)u4G z^p@F!)|9HhZprHd_vXjDoxs6WkK-6P0@lfxnGT>*p(QHoUV=u1FAqb@b%*W=a3{`LsH5k^AvQNL>6fPpy#oU(&MuH(*aEX4b35*} zn4n7)`I2U%=+Z=?BVZQ?vjQFW4gD@~XSOO6b{qu81`4&LFuU2(ilxW+1|ZkNMnWe79C$gs zWT?Ele|HR{JGPe)5BTW>0Ey?-Ls6S#GoV0tbt6ku7B&*0 z;i9QM$W1Rj*rRIdceL)rAOSl+sDe3LkB87<%){;ZdHp6|SNlopDXRx< zxBDF9-lTo&v`8$humFygUij@qgT=Qzhj8{ym2-{Xciwqq_Xwk%=O3B-MNAL_6e`3U zyxwmXex4`g0^1RYw~Dth3av3Dl^AAlpO3mG!nLr#&ZZ7c_wUboI+deC+&%TFjK2Lm z!Y&f1h|T_On%RCV&=4bx`!>(YezqGVhl&QpED?N6GV)HmzJ9&rh$x*i?*@o9#6QI< z5ZI_MRX;0+pY8$`j)eF#TlUyG(eE%E7S!rj;mj^M5vhUicPm zVWQ2z+imFyg}SRABmOBY_@osR!>7Ov!ioK`NB6_Rv}7Ud?35ed5Sb@?yND?kv~RCa wqs^a3Sh>&&L4)!LKI?D2&k@))k(LESaga|C278ChSzn3NWVkcuNoY&{0f?~U_5c6? literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-bg_flat_0_aaaaaa_40x100.png b/themefrontjs/webapp/thfrontjs/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000000000000000000000000000000000000..5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`R^ z$vje}bP0l+XkK DSH>_4 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-bg_flat_55_fbec88_40x100.png b/themefrontjs/webapp/thfrontjs/images/ui-bg_flat_55_fbec88_40x100.png new file mode 100644 index 0000000000000000000000000000000000000000..47acaadd737478ddb090f47f618810712163317b GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*Fsaj7L$B>N1x91EQ8x$BA993)+ za~~)OO5|O5sDCi_{N8&XlRv*c;OQ6|AR59NN?mFzWBXJVGojypu|S6~c)I$ztaD0e F0syyrGF|`x literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-bg_glass_75_d0e5f5_1x400.png b/themefrontjs/webapp/thfrontjs/images/ui-bg_glass_75_d0e5f5_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..9d149b1c61fdee2de5cb342741c243abb9058bb3 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAGVZ#9R3S1u{Kd978O6pI&z4VlotPxp?2+ z_9ElQf7546?+BK3Q+%u|q&R!~TD`8c>Nm?7lq=+T^Fw3At=o;a{QQ?&v0rW43t78u zKLhT~Z`;~2oo9{ezlW>%eD?g@v!}03FhOcY)5-`I-U~CHGCzC3{y9?4*Y0Z~JJ3=F MPgg&ebxsLQ09`{nxc~qF literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-bg_glass_85_dfeffc_1x400.png b/themefrontjs/webapp/thfrontjs/images/ui-bg_glass_85_dfeffc_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..014951529c315d6042e72febc310a4d2db5b4a82 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouq?|lm978O6-<~(*YA_IRxoBVf zfAX@vsV!R#l$@#*eLnw)_Sv|_?i7P!ORnX)SxaXh+BPpZ!Fw~yjr&#G|Jw^YMHDhV X&EsZx`7bsSXc~j3tDnm{r-UW|&(SK+ literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-bg_glass_95_fef1ec_1x400.png b/themefrontjs/webapp/thfrontjs/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..4443fdc1a156babad4336f004eaf5ca5dfa0f9ab GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnour0hIh978O6-<~(*YA|4MzBvER z|7}eQtdCVXoUc2b{PaWeaIKu7gJx>{vDV26o)#~38k_!`W=^oo1w6ixmPC4R1b Tyd6G3lNdZ*{an^LB{Ts5`idse literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png b/themefrontjs/webapp/thfrontjs/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png new file mode 100644 index 0000000000000000000000000000000000000000..81ecc362d50ef5abbc0420aacd5345822f1f6098 GIT binary patch literal 3457 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-icons_217bc0_256x240.png b/themefrontjs/webapp/thfrontjs/images/ui-icons_217bc0_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..6f4bd87c041453ef037c4a9a1a698f0105d02eba GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-icons_2e83ff_256x240.png b/themefrontjs/webapp/thfrontjs/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..09d1cdc856c292c4ab6dd818c7543ac0828bd616 GIT binary patch literal 4369 zcmd^?`8O2)_s3@pGmLE*`#M>&Z`mr_kcu#tBo!IbqU=l7VaSrbQrTh%5m}S08Obh0 zGL{*mi8RK}U~J#s@6Y%1S9~7lb?$xLU+y{go_o*h`AW1wUF3v{Kmh;%r@5J_9RL9Q zdj+hqg8o{9`K7(TZrR4t{=9O`!T-(~c=yEWZ{eswJJe->5bP8)t4;f(Y*i_HU*sLM z2=7-8guZ}@*(HhVC)Mqgr$3T8?#a(hu& z?Kzuw!O%PM>AicSW`_U(cbvJYv3{HfpIP~Q>@$^c588E$vv)V2c|Mr% zuFO$+I~Hg@u}wPm17n%}j1Y+Pbu!bt?iPkjGAo7>9eRN0FZz3X2_QZj+V!}+*8oBQ z_=iI^_TCA;Ea2tPmRNOeX3+VM>KL;o1(h`c@`6Ah`vdH<&+$yTg)jGWW72T}6J`kUAv?2CgyV zrs0y@Fpvpj@kWVE0TzL@Cy#qHn~kgensb{hIm6J&I8hkoNHOz6o1QQ3QM4NZyu?;= zLd>`wPT*uGr+6vAxYv3k8{gMDR>tO}UavDKzzyi6hvbuP=XQ4Y|A)r4#B$U(q7{1Z z0iLeSjo3;T*diS*me%4|!s23l@>R}rn@#Zc{<%CFt;?gd5S<)b=8Yz32U zBBLprntW3RE3f|uNX5Aw|I(IlJjW-Byd?QFFRk%hLU}O*YyYQel}WcXilLMJp9cB4 z)E?D+*Y4zai&XY!>niMfTW-2pp-^KFT93%Leig@uoQGPYRCva-`w#orm`is`p8b4s zxD462;f*^XO$=3by=VzN9i@xxr<1w=pcxl!$!fjWt|fYmq1@@badT?v`d zIi$|e$Ji}FXsiVYf)?pN1R0LBw;+)B5aUJj2fP+=m;=_Eho84g%Jq#@MLPSQEX*@T z6sZb)m?)zby>{j1)(;rRML|gKSs+9jorf-XhQJ2Jyt5Cqc*`S3iX@A5C3jvgAns|4 z*|)YQ%Kmsj+YZ53;nMqh|AFvehUV-9R;1ZZ;w5r9l}8hjSw@#k;>)$P*r%)=Extyu zB!$Kd-F?*50aJ2;TNTR-fc8B{KAq3!vW{g$LlGPfGW+%#CXU zJDcMsvyT2`x~v>>w8@yssoA`KuIZ98CLU{Ia%*nW3G4t}@ApsbC@o^WCqL>OXx>Y^ zSuVWEQ;3=A=@RxCnt0>G@#(VWBQ`0$qTwA#e>SX{_N~JWGsBxFHCw|5|?CzDi>92F-^=b*8sMXnhUJdb!>yGD2nhN@{582 zRPcxuDzs&;8De)>_J19z{0xppXQop#T_5ejGCKv@l>$O#DA-@X{y_1B-AsiU)H}DR z3xDZ8G`amV_WmA&8!W=@jgm|%bnwH%qkg(@J$hLaSV zC-rXIFMM%y<|Gb)o?j zpe-`dJ*N5tC-iH)d0CgLdBsw*C!ST9hY1EkI|Y(&=p&dH&q;a&7HXa5#_wtMsenQL zcpyhwx)Ppw@XmVz?P)DI#^ee1oC!i`>>Jq1ESk-OuQ(Pbv=s{A0AjM@rw#FaU;RUh z*At0{U*NtGVY_-JcuG$?zuuf%ZBTWxKU2yf?iN#-MRWs>A*2;p0G1Tp3d29u5RbnY zDOON-G|PidOOGeybnbzu7UVv71l!b=w7eU5l*{EdKuoKu`#LZ}|fnUr-+lSST9(MTT`0tqOG z#+Q_=lXe-=;rE4u8s~;%i~~ z8v&&+VPeXG=2zw9B5sR$e?R(n%nf?p-(BCZ8}x!_-9T+LT;2=Zu?Wv)j3#>35$6dR z4*7xmI)#06qjh#sXvX(%`#D1mD8fn1G~I;l%Dk{pw)}>_{+3^Fv_q)>2#de5qGCId zPz?ix-3954nM&u@vaw{o%-#HU%_bLJMO#@enR^&B{3ihWdoU6%pBJ`o>im+b-c6r-;c{vd0Z_)`75$jApy2?!9G4_FGa)iZ~9`6VELiYM+n!-mUfvfm{jt zC?!1=%pxJhF>vyQ47Q}R;O48pxgMs)rz$SbM&jkp<6X$r4DHWg>ZnGB-$r2o1*nL# zW0^*itcRY_^Uv^XgQP>W#>KQgM~l{;S(GkVW@&vld^AhWzG^m|9#0#USbM>^en{k2 za8~DTL`(Q~=ofsL&Fc`!L6r~qTnnGo8r98<(aG*<0%aNEr!!BIyY>VV82kxhR%d>V(lN&#BId#urK_i~Pe6?>C~J!pU_lRon#&S_cXoQv;poG8FK4atc

N)npz1~X%p6x{M(Gw!!H=!}lmO0Xr*8ewyH(Q+>oy`fxQkxJ zzzB$)%*xM4s_2(O>)T-QXhwP|&DZam#{O+47q|WKfz_ZL-MypRN~o{fE*I#6@eM?I zs%f-6{Lz6j7rB#U$%O$~TIT!j?|Ip1CpSmb=JA9qCY3-mQf|fVCxswPjok|VofUEP zW5^pTd5B;wRkyW%1a;nYHB$ef6Pv8^);`m0jv6p72iNJl+sVBqZugsq6cq_pyNREi z>GN!h6ZQ6`aOMr_2KI@j=XR@$aJj(2jcpY?>f=2kMV@di5W7Swj?ug10zRe}F1nR* ztMm6+T^)LJe^SzGgSxahQajq0h7#|8oMV0>D~*N}jl?9_X`ka42R4@rryDc3o(c$R?1*!1O9zleSOczw zYPS3~xbJ$~C(3+D7Zkrfjs_lneY^zv^kHmxt)aqZ!aeGABHZ`gvA&K`72z}ihI$Ht z9V&)wQy0g@R9irwbf!{uE&_J2l9jXz^Vj#=qA77*3Pd9OjrE_tKDHADd!AjFQv(ji zct-BMUt9()1Ox!dsI_h1(^F_U)_QJrx|%+y`zWWlD4=Nd?JQ=URh0*{fb1!o4tS(H z^r_T(8t1SAHf1oduG+X^*EC_kL(!QnXL6Hp);449yO&1xE>MXGqT)t10lzvALllX;;Q)RiJX$dm zlR8ep5-GdHmRm9?N#QCjNUA);vC03Gw6yds6^?c4;(MH>;O5xmQ2nGK3Dmk8i*v5t z-{jJsQq30%z}0`g7SN-yN`l-`@6rkJ|V|>18`MV zwUeH}DxWw&h+A+Dn|4|YNr&EfKS`Hz_NkeW3*sI5Rq-J&FzG=!{-K`n65#7O%^&f> z`PkqxyC_K)>781~7H${^Nj{`>XEa&OPqqQhySR5%w2{5+sEakXXHazJp6~LP2QKDx zpkvZrkDOa+A4BbqqX6ls&O)5-Q7`qkZ_?6~c-wQ9tseNtET;nhEOL^`*naKwcMX;R zbto&a;oTR0s;vjfj3wigUg)Sj)!OHQfZoJwAsWYI1A4ntz>X=W4s|y?tUk1r=>#Ct zf+?hq^>rQ3$KNboG$UhCdEmp{qAR13DK$f0ES7kAG~7q+g!jfVq`1b5+c62N^0%~o zKw91o@Wv;0EW*7fINAX3O~L-V{`;xB0q()#^HKZOlLrXVL*Dtw-$SUp8*_J{r( zW`6r`cz0yZQ#f0#*y+m64{bs7GP|2V$phf42rswJB?s@9qf;Bfc^pm-ZS#^5dkG{u zzv;l&B$NYcegSqAnjnPN1?17VUQbPummcWry((85IFB(pFQNGN{hhN$Fv?~l_fr?| z9=%dK(+;kZ(8=mwptjwC-ikBD$Z{l2++~*8wq5ynF<+PNlZI7ba5V#fg~L}kE;UH5 zJ;{P(`G{tNl&z5rUiH~e{I>GT8~9&*(J;Myx9z5P!db!F8RTII^I7c)HU=ss*bYB` zgwiIMZ_q>KEC$4lFm+Afvu6^$X1jm1rB*4H)-EIO5Rvz_p24?OkJ zovD4{-1KA6*oL?a;3qR7GZRB!cE5oAdA#M@{w+fGgsJ-lSmQ^-?8E&Q%tbmjd=@gZ z(}Mg*jsDf6Z)|7s%@9pc-tuw5W&zqUXjv2bVkC%-X?O3F72W4EsIl#1e>Mdz=X4k*_>VxCu_2?jjg16N*5fwC-36OW&;Sz}@jMn}hgJdEd pO;bST+>R{W-aENZYk%(=^(_R5N$LmL{Qc?!%+I4tt4z=_{|902Wu5>4 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-icons_469bdd_256x240.png b/themefrontjs/webapp/thfrontjs/images/ui-icons_469bdd_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..bd2cf079add1ca236adeb509698adabbffb08acb GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-icons_cd0a0a_256x240.png b/themefrontjs/webapp/thfrontjs/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab019b73ec11a485fa09378f3a0e155194f6a5d GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/images/ui-icons_f9bd01_256x240.png b/themefrontjs/webapp/thfrontjs/images/ui-icons_f9bd01_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..78625024d94a74eb1aceb327f45059faf439fece GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/javascript.css b/themefrontjs/webapp/thfrontjs/javascript.css new file mode 100644 index 000000000..811e4311f --- /dev/null +++ b/themefrontjs/webapp/thfrontjs/javascript.css @@ -0,0 +1,1966 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* ----------------------------------------------------------------------- */ +/* This CSS file is used for the Flat Grey visual theme JavaScript styles. */ +/* ----------------------------------------------------------------------- */ + +#wait-spinner { + z-index: 50000; + position: absolute; +} + +#wait-spinner-image { + height: 45px; + width: 45px; + background-repeat: no-repeat; + background-image: url(images/spinner.gif); +} + +.wait-spinner div { + padding-top: 4px; +} + +/*********************************************** +ajax auto complete +***********************************************/ +div.autocomplete { + position: absolute; + width: auto !important; + /* min-width:154px; */ + background-color: #f8f8f8; + border: 1px solid #999999; + margin: 0; + padding: 0; +} + +div.autocomplete ul { + list-style-type: none; + margin: 0; + padding: 0; +} + +div.autocomplete ul li.selected { + background-color: #ffb; +} + +div.autocomplete ul li { + list-style-type: none; + display: block; + float: none; + margin: 0; + padding: 2px; + /*height:32px;*/ + cursor: pointer; +} + +.indicator img { + background: url(images/ajax-loader.gif) no-repeat 0 0; + display: block; + margin: 3px 10px 0px 24px; + width: 16px; + height: 16px; + border: none; +} + +.screenlet-title-bar .indicator img { + float: right; + margin: 0 3px; + width: 16px; + height: 16px; + display: inline; +} + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} + +.ui-helper-hidden-accessible { + position: absolute !important; + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); +} + +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} + +.ui-helper-clearfix:before, .ui-helper-clearfix:after { + content: ""; + display: table; +} + +.ui-helper-clearfix:after { + clear: both; +} + +.ui-helper-clearfix { + zoom: 1; +} + +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter: Alpha(Opacity=0); +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.ui-resizable { + position: relative; +} + +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; +} + +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { + display: none; +} + +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} + +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} + +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} + +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} + +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} + +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} + +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} + +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} + +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} + +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin-top: 2px; + padding: .5em .5em .5em .7em; + zoom: 1; +} + +.ui-accordion .ui-accordion-icons { + padding-left: 2.2em; +} + +.ui-accordion .ui-accordion-noicons { + padding-left: .7em; +} + +.ui-accordion .ui-accordion-icons .ui-accordion-icons { + padding-left: 2.2em; +} + +.ui-accordion .ui-accordion-header .ui-accordion-header-icon { + position: absolute; + left: .5em; + top: 50%; + margin-top: -8px; +} + +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; + zoom: 1; +} + +.ui-autocomplete { + position: absolute; + cursor: default; +} + +/* workarounds */ +* html .ui-autocomplete { + width: 1px; +} + +/* without this, the menu expands to 100% in IE6 */ +.ui-button { + display: inline-block; + position: relative; + padding: 0; + margin-right: .1em; + cursor: pointer; + text-align: center; + zoom: 1; + overflow: visible; +} + +/* the overflow property removes extra width in IE */ +.ui-button, .ui-button:link, .ui-button:visited, .ui-button:hover, .ui-button:active { + text-decoration: none; +} + +.ui-button-icon-only { + width: 2.2em; +} + +/* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { + width: 2.4em; +} + +/* button elements seem to need a little more width */ +.ui-button-icons-only { + width: 3.4em; +} + +button.ui-button-icons-only { + width: 3.7em; +} + +/*button text element */ +.ui-button .ui-button-text { + display: block; + line-height: 1.4; +} + +.ui-button-text-only .ui-button-text { + padding: .4em 1em; +} + +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { + padding: .4em; + text-indent: -9999999px; +} + +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { + padding: .4em 1em .4em 2.1em; +} + +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { + padding: .4em 2.1em .4em 1em; +} + +.ui-button-text-icons .ui-button-text { + padding-left: 2.1em; + padding-right: 2.1em; +} + +/* no icon support for input elements, provide padding by default */ +input.ui-button { + padding: .4em 1em; +} + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { + position: absolute; + top: 50%; + margin-top: -8px; +} + +.ui-button-icon-only .ui-icon { + left: 50%; + margin-left: -8px; +} + +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { + left: .5em; +} + +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} + +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} + +/*button sets*/ +.ui-buttonset { + margin-right: 7px; +} + +.ui-buttonset .ui-button { + margin-left: 0; + margin-right: -.3em; +} + +/* workarounds */ +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* reset extra padding in Firefox */ +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} + +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} + +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} + +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} + +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} + +.ui-datepicker .ui-datepicker-next { + right: 2px; +} + +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} + +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} + +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} + +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} + +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} + +.ui-datepicker select.ui-datepicker-month-year { + width: 100%; +} + +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 49%; +} + +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} + +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} + +.ui-datepicker td { + border: 0; + padding: 1px; +} + +.ui-datepicker td span, .ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} + +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} + +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} + +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} + +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} + +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} + +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} + +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} + +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} + +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { + border-left-width: 0; +} + +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} + +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} + +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0em; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} + +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} + +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} + +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} + +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} + +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} + +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} + +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { + float: right; +} + +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} + +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} + +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +} + +.ui-dialog { + position: absolute; + top: 0; + left: 0; + padding: .2em; + width: 300px; + overflow: hidden; +} + +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + height: 16px; + position: relative; +} + +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 16px .1em 0; +} + +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 19px; + margin: -10px 0 0 0; + padding: 1px; + height: 18px; +} + +/*.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }*/ +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { + padding: 0; +} + +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; + zoom: 1; +} + +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin: .5em 0 0 0; + padding: .3em 1em .5em .4em; +} + +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} + +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} + +.ui-dialog .ui-resizable-se { + width: 14px; + height: 14px; + right: 3px; + bottom: 3px; +} + +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} + +.ui-menu { + list-style: none; + padding: 2px; + margin: 0; + display: block; + outline: none; +} + +.ui-menu .ui-menu { + margin-top: -3px; + position: absolute; +} + +.ui-menu .ui-menu-item { + margin: 0; + padding: 0; + zoom: 1; + width: 100%; +} + +.ui-menu .ui-menu-divider { + margin: 5px -2px 5px -2px; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} + +.ui-menu .ui-menu-item a { + text-decoration: none; + display: block; + padding: 2px .4em; + line-height: 1.5; + zoom: 1; + font-weight: normal; +} + +.ui-menu .ui-menu-item a.ui-state-focus, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} + +.ui-menu .ui-state-disabled { + font-weight: normal; + margin: .4em 0 .2em; + line-height: 1.5; +} + +.ui-menu .ui-state-disabled a { + cursor: default; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} + +.ui-menu-icons .ui-menu-item a { + position: relative; + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: .2em; + left: .2em; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + position: static; + float: right; +} + +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} + +.ui-progressbar .ui-progressbar-value { + margin: -10px; + height: 100%; +} + +.ui-slider { + position: relative; + text-align: left; +} + +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; +} + +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +.ui-slider-horizontal { + height: .8em; +} + +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} + +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} + +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} + +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} + +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} + +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} + +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} + +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} + +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} + +.ui-spinner-input { + border: none; + background: none; + padding: 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 22px; +} + +.ui-spinner-button { + width: 16px; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + z-index: 100; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} + +.ui-spinner a.ui-spinner-button { + border-top: none; + border-bottom: none; + border-right: none; +} + +/* more specificity required here to overide default borders */ +.ui-spinner .ui-icon { + position: absolute; + margin-top: -8px; + top: 50%; + left: 0; +} + +/* vertical centre icon */ +.ui-spinner-up { + top: 0; +} + +.ui-spinner-down { + bottom: 0; +} + +/* TR overrides */ +span.ui-spinner { + background: none; +} + +.ui-spinner .ui-icon-triangle-1-s { + /* need to fix icons sprite */ + background-position: -65px -16px; +} + +.ui-tabs { + position: relative; + padding: .2em; + zoom: 1; +} + +/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} + +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom: 0; + padding: 0; + white-space: nowrap; +} + +.ui-tabs .ui-tabs-nav li a { + float: left; + padding: .5em 1em; + text-decoration: none; +} + +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} + +.ui-tabs .ui-tabs-nav li.ui-tabs-active a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-tabs-loading a { + cursor: text; +} + +.ui-tabs .ui-tabs-nav li a, .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a { + cursor: pointer; +} + +/* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} + +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + -o-box-shadow: 0 0 5px #aaa; + -moz-box-shadow: 0 0 5px #aaa; + -webkit-box-shadow: 0 0 5px #aaa; + box-shadow: 0 0 5px #aaa; +} + +/* Fades and background-images don't work well together in IE6, drop the image */ +* html .ui-tooltip { + background-image: none; +} + +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-size: 1.1em; +} + +.ui-widget .ui-widget { + font-size: 1em; +} + +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { + font-size: 1em; +} + +.ui-widget-content { + border: 1px solid #ccc; + background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; + color: #222222; +} + +.ui-widget-content a { + color: #222222; +} + +.ui-widget-header { + border: 1px solid #5d819d; + background: #5d819d url(images/main-nav.gif) top left repeat-x; + color: #ffffff; + font-weight: bold; +} + +.ui-widget-header a { + color: #ffffff; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { + border: 1px solid #c5dbec; + background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; + font-weight: bold; + color: #2e6e9e; +} + +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { + color: #2e6e9e; + text-decoration: none; +} + +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { + border: 1px solid #79b7e7; + background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; + font-weight: bold; + color: #1d5987; +} + +.ui-state-hover a, .ui-state-hover a:hover { + color: #1d5987; + text-decoration: none; +} + +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { + border: 1px solid #79b7e7; + background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; + font-weight: bold; + color: #e17009; +} + +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { + color: #e17009; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { + border: 1px solid #fad42e; + background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; + color: #363636; +} + +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a, .ui-widget-header .ui-state-highlight a { + color: #363636; +} + +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; + color: #cd0a0a; +} + +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { + color: #cd0a0a; +} + +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} + +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { + font-weight: bold; +} + +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { + opacity: .7; + filter: Alpha(Opacity=70); + font-weight: normal; +} + +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { + opacity: .35; + filter: Alpha(Opacity=35); + background-image: none; +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; + background-image: url(images/ui-icons_469bdd_256x240.png); +} + +.ui-widget-content .ui-icon { + background-image: url(images/ui-icons_469bdd_256x240.png); +} + +.ui-widget-header .ui-icon { + background-image: url(images/ui-icons_d8e7f3_256x240.png); +} + +.ui-state-default .ui-icon { + background-image: url(images/ui-icons_6da8d5_256x240.png); +} + +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon { + background-image: url(images/ui-icons_217bc0_256x240.png); +} + +.ui-state-active .ui-icon { + background-image: url(images/ui-icons_f9bd01_256x240.png); +} + +.ui-state-highlight .ui-icon { + background-image: url(images/ui-icons_2e83ff_256x240.png); +} + +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon { + background-image: url(images/ui-icons_cd0a0a_256x240.png); +} + +/* positioning */ +.ui-icon-carat-1-n { + background-position: 0 0; +} + +.ui-icon-carat-1-ne { + background-position: -16px 0; +} + +.ui-icon-carat-1-e { + background-position: -32px 0; +} + +.ui-icon-carat-1-se { + background-position: -48px 0; +} + +.ui-icon-carat-1-s { + background-position: -64px 0; +} + +.ui-icon-carat-1-sw { + background-position: -80px 0; +} + +.ui-icon-carat-1-w { + background-position: -96px 0; +} + +.ui-icon-carat-1-nw { + background-position: -112px 0; +} + +.ui-icon-carat-2-n-s { + background-position: -128px 0; +} + +.ui-icon-carat-2-e-w { + background-position: -144px 0; +} + +.ui-icon-triangle-1-n { + background-position: 0 -16px; +} + +.ui-icon-triangle-1-ne { + background-position: -16px -16px; +} + +.ui-icon-triangle-1-e { + background-position: -32px -16px; +} + +.ui-icon-triangle-1-se { + background-position: -48px -16px; +} + +.ui-icon-triangle-1-s { + background-position: -64px -16px; +} + +.ui-icon-triangle-1-sw { + background-position: -80px -16px; +} + +.ui-icon-triangle-1-w { + background-position: -96px -16px; +} + +.ui-icon-triangle-1-nw { + background-position: -112px -16px; +} + +.ui-icon-triangle-2-n-s { + background-position: -128px -16px; +} + +.ui-icon-triangle-2-e-w { + background-position: -144px -16px; +} + +.ui-icon-arrow-1-n { + background-position: 0 -32px; +} + +.ui-icon-arrow-1-ne { + background-position: -16px -32px; +} + +.ui-icon-arrow-1-e { + background-position: -32px -32px; +} + +.ui-icon-arrow-1-se { + background-position: -48px -32px; +} + +.ui-icon-arrow-1-s { + background-position: -64px -32px; +} + +.ui-icon-arrow-1-sw { + background-position: -80px -32px; +} + +.ui-icon-arrow-1-w { + background-position: -96px -32px; +} + +.ui-icon-arrow-1-nw { + background-position: -112px -32px; +} + +.ui-icon-arrow-2-n-s { + background-position: -128px -32px; +} + +.ui-icon-arrow-2-ne-sw { + background-position: -144px -32px; +} + +.ui-icon-arrow-2-e-w { + background-position: -160px -32px; +} + +.ui-icon-arrow-2-se-nw { + background-position: -176px -32px; +} + +.ui-icon-arrowstop-1-n { + background-position: -192px -32px; +} + +.ui-icon-arrowstop-1-e { + background-position: -208px -32px; +} + +.ui-icon-arrowstop-1-s { + background-position: -224px -32px; +} + +.ui-icon-arrowstop-1-w { + background-position: -240px -32px; +} + +.ui-icon-arrowthick-1-n { + background-position: 0 -48px; +} + +.ui-icon-arrowthick-1-ne { + background-position: -16px -48px; +} + +.ui-icon-arrowthick-1-e { + background-position: -32px -48px; +} + +.ui-icon-arrowthick-1-se { + background-position: -48px -48px; +} + +.ui-icon-arrowthick-1-s { + background-position: -64px -48px; +} + +.ui-icon-arrowthick-1-sw { + background-position: -80px -48px; +} + +.ui-icon-arrowthick-1-w { + background-position: -96px -48px; +} + +.ui-icon-arrowthick-1-nw { + background-position: -112px -48px; +} + +.ui-icon-arrowthick-2-n-s { + background-position: -128px -48px; +} + +.ui-icon-arrowthick-2-ne-sw { + background-position: -144px -48px; +} + +.ui-icon-arrowthick-2-e-w { + background-position: -160px -48px; +} + +.ui-icon-arrowthick-2-se-nw { + background-position: -176px -48px; +} + +.ui-icon-arrowthickstop-1-n { + background-position: -192px -48px; +} + +.ui-icon-arrowthickstop-1-e { + background-position: -208px -48px; +} + +.ui-icon-arrowthickstop-1-s { + background-position: -224px -48px; +} + +.ui-icon-arrowthickstop-1-w { + background-position: -240px -48px; +} + +.ui-icon-arrowreturnthick-1-w { + background-position: 0 -64px; +} + +.ui-icon-arrowreturnthick-1-n { + background-position: -16px -64px; +} + +.ui-icon-arrowreturnthick-1-e { + background-position: -32px -64px; +} + +.ui-icon-arrowreturnthick-1-s { + background-position: -48px -64px; +} + +.ui-icon-arrowreturn-1-w { + background-position: -64px -64px; +} + +.ui-icon-arrowreturn-1-n { + background-position: -80px -64px; +} + +.ui-icon-arrowreturn-1-e { + background-position: -96px -64px; +} + +.ui-icon-arrowreturn-1-s { + background-position: -112px -64px; +} + +.ui-icon-arrowrefresh-1-w { + background-position: -128px -64px; +} + +.ui-icon-arrowrefresh-1-n { + background-position: -144px -64px; +} + +.ui-icon-arrowrefresh-1-e { + background-position: -160px -64px; +} + +.ui-icon-arrowrefresh-1-s { + background-position: -176px -64px; +} + +.ui-icon-arrow-4 { + background-position: 0 -80px; +} + +.ui-icon-arrow-4-diag { + background-position: -16px -80px; +} + +.ui-icon-extlink { + background-position: -32px -80px; +} + +.ui-icon-newwin { + background-position: -48px -80px; +} + +.ui-icon-refresh { + background-position: -64px -80px; +} + +.ui-icon-shuffle { + background-position: -80px -80px; +} + +.ui-icon-transfer-e-w { + background-position: -96px -80px; +} + +.ui-icon-transferthick-e-w { + background-position: -112px -80px; +} + +.ui-icon-folder-collapsed { + background-position: 0 -96px; +} + +.ui-icon-folder-open { + background-position: -16px -96px; +} + +.ui-icon-document { + background-position: -32px -96px; +} + +.ui-icon-document-b { + background-position: -48px -96px; +} + +.ui-icon-note { + background-position: -64px -96px; +} + +.ui-icon-mail-closed { + background-position: -80px -96px; +} + +.ui-icon-mail-open { + background-position: -96px -96px; +} + +.ui-icon-suitcase { + background-position: -112px -96px; +} + +.ui-icon-comment { + background-position: -128px -96px; +} + +.ui-icon-person { + background-position: -144px -96px; +} + +.ui-icon-print { + background-position: -160px -96px; +} + +.ui-icon-trash { + background-position: -176px -96px; +} + +.ui-icon-locked { + background-position: -192px -96px; +} + +.ui-icon-unlocked { + background-position: -208px -96px; +} + +.ui-icon-bookmark { + background-position: -224px -96px; +} + +.ui-icon-tag { + background-position: -240px -96px; +} + +.ui-icon-home { + background-position: 0 -112px; +} + +.ui-icon-flag { + background-position: -16px -112px; +} + +.ui-icon-calendar { + background-position: -32px -112px; +} + +.ui-icon-cart { + background-position: -48px -112px; +} + +.ui-icon-pencil { + background-position: -64px -112px; +} + +.ui-icon-clock { + background-position: -80px -112px; +} + +.ui-icon-disk { + background-position: -96px -112px; +} + +.ui-icon-calculator { + background-position: -112px -112px; +} + +.ui-icon-zoomin { + background-position: -128px -112px; +} + +.ui-icon-zoomout { + background-position: -144px -112px; +} + +.ui-icon-search { + background-position: -160px -112px; +} + +.ui-icon-wrench { + background-position: -176px -112px; +} + +.ui-icon-gear { + background-position: -192px -112px; +} + +.ui-icon-heart { + background-position: -208px -112px; +} + +.ui-icon-star { + background-position: -224px -112px; +} + +.ui-icon-link { + background-position: -240px -112px; +} + +.ui-icon-cancel { + background-position: 0 -128px; +} + +.ui-icon-plus { + background-position: -16px -128px; +} + +.ui-icon-plusthick { + background-position: -32px -128px; +} + +.ui-icon-minus { + background-position: -48px -128px; +} + +.ui-icon-minusthick { + background-position: -64px -128px; +} + +.ui-icon-close { + background-position: -80px -128px; +} + +.ui-icon-closethick { + background-position: -96px -128px; +} + +.ui-icon-key { + background-position: -112px -128px; +} + +.ui-icon-lightbulb { + background-position: -128px -128px; +} + +.ui-icon-scissors { + background-position: -144px -128px; +} + +.ui-icon-clipboard { + background-position: -160px -128px; +} + +.ui-icon-copy { + background-position: -176px -128px; +} + +.ui-icon-contact { + background-position: -192px -128px; +} + +.ui-icon-image { + background-position: -208px -128px; +} + +.ui-icon-video { + background-position: -224px -128px; +} + +.ui-icon-script { + background-position: -240px -128px; +} + +.ui-icon-alert { + background-position: 0 -144px; +} + +.ui-icon-info { + background-position: -16px -144px; +} + +.ui-icon-notice { + background-position: -32px -144px; +} + +.ui-icon-help { + background-position: -48px -144px; +} + +.ui-icon-check { + background-position: -64px -144px; +} + +.ui-icon-bullet { + background-position: -80px -144px; +} + +.ui-icon-radio-on { + background-position: -96px -144px; +} + +.ui-icon-radio-off { + background-position: -112px -144px; +} + +.ui-icon-pin-w { + background-position: -128px -144px; +} + +.ui-icon-pin-s { + background-position: -144px -144px; +} + +.ui-icon-play { + background-position: 0 -160px; +} + +.ui-icon-pause { + background-position: -16px -160px; +} + +.ui-icon-seek-next { + background-position: -32px -160px; +} + +.ui-icon-seek-prev { + background-position: -48px -160px; +} + +.ui-icon-seek-end { + background-position: -64px -160px; +} + +.ui-icon-seek-start { + background-position: -80px -160px; +} + +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { + background-position: -80px -160px; +} + +.ui-icon-stop { + background-position: -96px -160px; +} + +.ui-icon-eject { + background-position: -112px -160px; +} + +.ui-icon-volume-off { + background-position: -128px -160px; +} + +.ui-icon-volume-on { + background-position: -144px -160px; +} + +.ui-icon-power { + background-position: 0 -176px; +} + +.ui-icon-signal-diag { + background-position: -16px -176px; +} + +.ui-icon-signal { + background-position: -32px -176px; +} + +.ui-icon-battery-0 { + background-position: -48px -176px; +} + +.ui-icon-battery-1 { + background-position: -64px -176px; +} + +.ui-icon-battery-2 { + background-position: -80px -176px; +} + +.ui-icon-battery-3 { + background-position: -96px -176px; +} + +.ui-icon-circle-plus { + background-position: 0 -192px; +} + +.ui-icon-circle-minus { + background-position: -16px -192px; +} + +.ui-icon-circle-close { + background-position: -32px -192px; +} + +.ui-icon-circle-triangle-e { + background-position: -48px -192px; +} + +.ui-icon-circle-triangle-s { + background-position: -64px -192px; +} + +.ui-icon-circle-triangle-w { + background-position: -80px -192px; +} + +.ui-icon-circle-triangle-n { + background-position: -96px -192px; +} + +.ui-icon-circle-arrow-e { + background-position: -112px -192px; +} + +.ui-icon-circle-arrow-s { + background-position: -128px -192px; +} + +.ui-icon-circle-arrow-w { + background-position: -144px -192px; +} + +.ui-icon-circle-arrow-n { + background-position: -160px -192px; +} + +.ui-icon-circle-zoomin { + background-position: -176px -192px; +} + +.ui-icon-circle-zoomout { + background-position: -192px -192px; +} + +.ui-icon-circle-check { + background-position: -208px -192px; +} + +.ui-icon-circlesmall-plus { + background-position: 0 -208px; +} + +.ui-icon-circlesmall-minus { + background-position: -16px -208px; +} + +.ui-icon-circlesmall-close { + background-position: -32px -208px; +} + +.ui-icon-squaresmall-plus { + background-position: -48px -208px; +} + +.ui-icon-squaresmall-minus { + background-position: -64px -208px; +} + +.ui-icon-squaresmall-close { + background-position: -80px -208px; +} + +.ui-icon-grip-dotted-vertical { + background-position: 0 -224px; +} + +.ui-icon-grip-dotted-horizontal { + background-position: -16px -224px; +} + +.ui-icon-grip-solid-vertical { + background-position: -32px -224px; +} + +.ui-icon-grip-solid-horizontal { + background-position: -48px -224px; +} + +.ui-icon-gripsmall-diagonal-se { + background-position: -64px -224px; +} + +.ui-icon-grip-diagonal-se { + background-position: -80px -224px; +} + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { + -moz-border-radius-topleft: 5px; + -webkit-border-top-left-radius: 5px; + -khtml-border-top-left-radius: 5px; + border-top-left-radius: 5px; +} + +.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { + -moz-border-radius-topright: 5px; + -webkit-border-top-right-radius: 5px; + -khtml-border-top-right-radius: 5px; + border-top-right-radius: 5px; +} + +.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { + -moz-border-radius-bottomleft: 5px; + -webkit-border-bottom-left-radius: 5px; + -khtml-border-bottom-left-radius: 5px; + border-bottom-left-radius: 5px; +} + +.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { + -moz-border-radius-bottomright: 5px; + -webkit-border-bottom-right-radius: 5px; + -khtml-border-bottom-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); +} + +.ui-widget-shadow { + margin: -8px 0 0 -8px; + padding: 8px; + background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + -webkit-border-radius: 8px; + border-radius: 8px; +} + +/* Calendar Button and Image */ +.view-calendar button { + background: url(/images/cal.gif) no-repeat !important; + display: inline-block; + border: none; + vertical-align: middle; + /*wt;begin/end*/ + margin: 2px 2px 2px 2px; + height: 18px; + width: 18px; +} + +/* css for timepicker */ +.ui-timepicker-div .ui-widget-header { + margin-bottom: 8px; +} + +.ui-timepicker-div dl { + text-align: left; +} + +.ui-timepicker-div dl dt { + height: 25px; + margin-bottom: -25px; +} + +.ui-timepicker-div dl dd { + margin: 0 10px 10px 65px; +} + +.ui-timepicker-div td { + font-size: 90%; +} + +.ui-tpicker-grid-label { + background: none; + border: none; + margin: 0; + padding: 0; +} + +/*set Jgrowl css here to avoid side effect with other class*/ +.errorMessageJGrowl { + background: url("/flatgrey/images/exclamation.png") no-repeat scroll 5px 50% #820f05; + border: 3px solid #f0f0f0; + color: #ffffff; + font-size: 1.3em; + font-weight: bold; + padding: 10px 10px 10px 50px; + line-height: 1.3em; + min-width: 350px; + word-wrap: break-word; + box-shadow: -0px -0px 20px 5px #888888; + text-overflow: ellipsis; +} + +.eventMessageJGrowl { + background: url("/flatgrey/images/information.png") no-repeat scroll 5px 50% #105fa8; + border: 3px solid #f0f0f0; + color: #ffffff; + font-size: 1.3em; + font-weight: bold; + padding: 10px 10px 10px 50px; + line-height: 1.3em; + min-width: 350px; + word-wrap: break-word; + box-shadow: -0px -0px 20px 5px #888888; +} + +.closeAllJGrowl { + min-width: 350px; + padding: 5px 5px 5px 50px; + border: 1px solid #f0f0f0; +} + +.content-messages p { + display: none; +} + diff --git a/themefrontjs/webapp/thfrontjs/js/application.js b/themefrontjs/webapp/thfrontjs/js/application.js new file mode 100644 index 000000000..9a30e73b7 --- /dev/null +++ b/themefrontjs/webapp/thfrontjs/js/application.js @@ -0,0 +1,201 @@ +/*********************************************** +APACHE OFBiz +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +***********************************************/ + +/********************* +JQuery Columns +*********************/ +var j = 1; + +(function(jQuery) { + jQuery.fn.columns = function(options) { + + var defaults = { + colNumber: 2, + direction: 'vertical' + }; + + this.each(function() { + + var obj = jQuery(this); + var settings = jQuery.extend(defaults, options); + var totalListElements = jQuery(this).children('li').size(); + var baseColItems = Math.ceil(totalListElements / settings.colNumber); + var listClass = jQuery(this).attr('class'); + + for (i=1;i<=settings.colNumber;i++) { + if(i==1){ + jQuery(this).addClass('listCol1').wrap('

'); + } + else if(jQuery(this).is('ul')) { + jQuery(this).parents('.listContainer'+j).append('
    '); + } + else { + jQuery(this).parents('.listContainer'+j).append('
      '); + } + jQuery('.listContainer'+j+' > ul,.listContainer'+j+' > ol').addClass(listClass); + } + + var listItem = 0; + var k = 1; + var l = 0; + + if(settings.direction == 'vertical') { + jQuery(this).children('li').each(function() { + listItem = listItem+1; + if (listItem > baseColItems*(settings.colNumber-1) ) { + jQuery(this).parents('.listContainer'+j).find('.listCol'+settings.colNumber).append(this); + } + else { + if(listItem<=(baseColItems*k)) { + jQuery(this).parents('.listContainer'+j).find('.listCol'+k).append(this); + } + else { + jQuery(this).parents('.listContainer'+j).find('.listCol'+(k+1)).append(this); + k = k+1; + } + } + }); + + jQuery('.listContainer'+j).find('ol,ul').each(function(){ + if(jQuery(this).children().size() == 0) { + jQuery(this).remove(); + } + }); + + } + + else { + jQuery(this).children('li').each(function(){ + l = l+1; + if(l <= settings.colNumber) { + jQuery(this).parents('.listContainer'+j).find('.listCol'+l).append(this); + } + else { + l = 1; + jQuery(this).parents('.listContainer'+j).find('.listCol'+l).append(this); + } + }); + } + + jQuery('.listContainer'+j).find('ol:last,ul:last').addClass('last'); + j = j+1; + + }); + }; +})(jQuery); + +/********************* +JQuery Formalize +*********************/ +var FORMALIZE = (function($, window, document, undefined) { + var PLACEHOLDER_SUPPORTED = 'placeholder' in document.createElement('input'); + var AUTOFOCUS_SUPPORTED = 'autofocus' in document.createElement('input'); + var WEBKIT = 'webkitAppearance' in document.createElement('select').style; + var IE6 = !!($.browser.msie && parseInt($.browser.version, 10) === 6); + var IE7 = !!($.browser.msie && parseInt($.browser.version, 10) === 7); + return { + go: function() { + for (var i in FORMALIZE.init) { + FORMALIZE.init[i](); + } + }, + init: { + detect_webkit: function() { + if (!WEBKIT) { + return; + } + $('html').addClass('is_webkit'); + }, + full_input_size: function() { + if (!IE7 || !$('textarea, input.input_full').length) { + return; + } + $('textarea, input.input_full').wrap(''); + }, + ie6_skin_inputs: function() { + if (!IE6 || !$('input, select, textarea').length) { + return; + } + var button_regex = /button|submit|reset/; + var type_regex = /date|datetime|datetime-local|email|month|number|password|range|search|tel|text|time|url|week/; + $('input').each(function() { + var el = $(this); + if (this.getAttribute('type').match(button_regex)) { + el.addClass('ie6_button'); + if (this.disabled) { + el.addClass('ie6_button_disabled'); + } + } + else if (this.getAttribute('type').match(type_regex)) { + el.addClass('ie6_input'); + if (this.disabled) { + el.addClass('ie6_input_disabled'); + } + } + }); + $('textarea, select').each(function() { + if (this.disabled) { + $(this).addClass('ie6_input_disabled'); + } + }); + }, + placeholder: function() { + if (PLACEHOLDER_SUPPORTED || !$(':input[placeholder]').length) { + return; + } + $(':input[placeholder]').each(function() { + var el = $(this); + var text = el.attr('placeholder'); + function add_placeholder() { + if (!el.val() || el.val() === text) { + el.val(text).addClass('placeholder_text'); + } + } + add_placeholder(); + el.focus(function() { + if (el.val() === text) { + el.val('').removeClass('placeholder_text');; + } + }).blur(function() { + add_placeholder(); + }); + el.closest('form').submit(function() { + if (el.val() === text) { + el.val(''); + } + }).on('reset', function() { + setTimeout(add_placeholder, 50); + }); + }); + }, + autofocus: function() { + if (AUTOFOCUS_SUPPORTED || !$(':input[autofocus]').length) { + return; + } + $(':input[autofocus]:visible:first').select(); + } + } + }; +})(jQuery, this, this.document); + +jQuery(document).ready(function() { + FORMALIZE.go(); +}); + diff --git a/themefrontjs/webapp/thfrontjs/screenshot.jpg b/themefrontjs/webapp/thfrontjs/screenshot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..68341caa9225501cfe9edbb89c32bc25ba76e767 GIT binary patch literal 57197 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

      ?m4m!`Nhc%~~ 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

      ~Nnqrl}48Q;2BT2hqa=IFhY6pS85LC=nP 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 XBX<&yjR0o}U9d$EJ*`u@`ThR@muWsw literal 0 HcmV?d00001 diff --git a/themefrontjs/webapp/thfrontjs/style.css b/themefrontjs/webapp/thfrontjs/style.css new file mode 100644 index 000000000..c4e953501 --- /dev/null +++ b/themefrontjs/webapp/thfrontjs/style.css @@ -0,0 +1,380 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* ----------------------------------------------------- */ +/* This CSS file is used for the Flat Grey visual theme. */ +/* ----------------------------------------------------- */ + +/* This theme uses a scalable, floating layout - so most + sizes are expressed in EMs. PX sizes are used only for + drop shadows and positioning around graphic images. */ + +/* -------------------------------------- */ +/* Resets/Browser Normalization */ +/* -------------------------------------- */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + border: 0; + margin: 0; + outline: 0; + padding: 0; + font-size: 100%; + background: transparent; + vertical-align: baseline; +} + +blockquote, body, div, dl, dt, dd, fieldset, form, h1, h2, h3, h4, h5, h6, +img, input, li, ol, p, select, span, td, textarea, th, ul { + border-style: none; + margin: 0; + padding: 0; +} + +a { + text-decoration: none; +} + +body, caption, th { + text-align: left; +} + +ol, ul { + list-style: none; +} + +ol.numbers { + list-style: decimal; + padding-left: 20px; +} + +ul.dots { + list-style: circle; + padding-left: 20px; +} + +/* ------------------------------ */ +/* Basic Element Styles */ +/* ------------------------------ */ + + +.disabled { + color: #333366; + text-decoration: none; +} + +html { + background-image: url(/flatgrey/images/tile.gif); +} + +hr { + background-color: #ccc; + color: #ccc; + border-bottom: #fafafa 0.1em solid; + border-right: #fafafa 0.2em solid; + border-left: #d9d9d9 0.2em solid; + border-top: #d9d9d9 0.1em solid; + margin: 0.5em 0 0.5em 0; +} + +h1, .h1 { + font-size: 1.6em; + font-weight: bold; +} + +h2, .h2 { + font-size: 1.3em; + font-weight: bold; +} + +h3, .h3 { + font-size: 1.1em; + font-weight: bold; +} + +th, th a { + color: #000000; + font-weight: bold; +} + +/* -------------------------- */ +/* Float Clear/Reset */ +/* -------------------------- */ + +.clear { + clear: both; + height: 0; + visibility: hidden; +} + +.no-clear .clear { + /* Used in container elements to override + the clear class in contained elements. */ + clear: none; +} + +/* ------------------------------- */ +/* Single Element Styles */ +/* ------------------------------- */ + +.alert { + /* Used for alert text */ + color: #ff0000; +} + +.content-messages p { + margin: 0.5em 0 0.5em 0; +} + +.hidden { + height: 0; + visibility: hidden; +} + + +.tooltip { + color: #557996; + font-style: italic; + margin: 0.4em; +} + +.visible { + height: auto; + visibility: visible; +} + +.page-container { + /*background: url("images/big-fade.png") repeat-x 0 90px transparent;*/ +} + +.align-float { + float: right; +} + +.align-text { + text-align: right; +} + +.align-top { + vertical-align: top; +} + +.centered { + text-align: center; +} + +.page-title { + color: #557996; + margin-bottom: 0.5em; + font-size: 1.6em; + font-weight: bold; +} + +.contentarea { +} + +/* ------------------------ */ +/* Masthead Style */ +/* ------------------------ */ + +#masthead { + background: #47637c url(images/masthead.gif) top left repeat-x; + color: #ccc; + font-size: 1em; + font-weight: normal; + min-height: 26px; /* Setting must be in px */ + height: 31px; + overflow: auto; +} + +#masthead ul li { + float: left; + padding: 0.5em 0.8em 0.5em 0; +} + +#masthead ul li a { + color: #ccc; + font-weight: normal; +} + +.last-system-msg { + color: #FFFFFF; + font-size: 0.9em; + padding-left: 10px; +} + +.preference-area { + float: right !important; +} + +.preference-area li { + font-size: 0.6em; +} + +#masthead ul .logo-area { + padding: 0.1em 0.8em; +} +#masthead ul .logo-area img { + height: 60px; + width: 129px; +} + +#masthead ul .org-logo-area { + padding: 0.1em 0.8em; +} + +#masthead ul .org-logo-area img { + width: auto; + height: 22px; +} + +#masthead ul a:focus { + text-decoration: underline; +} + +/* ---------------------- */ +/* Footer Style */ +/* ---------------------- */ + +#footer { + background: #333; + border-top: 0.1em inset #000; + padding: 0.5em 0 0.5em 0.5em; + margin-left: 80px +} + +#footer a { + color: #ccc; + font-weight: normal; +} + +#footer ul { + float: left; + display: inline; + font-size: 0.6em; +} + +#footer ul li { + border-left: 0.1em solid #444; + border-right: 0.1em solid #222; + float: left; + display: inline; + color: #ccc; + font-weight: normal; + padding: 0.5em; +} + +#footer ul .first { + border-left: none; +} + +#footer ul .last { + border-right: none; +} + +#footer ul li a { + color: #ccc; + font-weight: normal; +} + +#footer p { + clear: left; + color: #ccc; + font-weight: normal; + padding: 0.5em; +} + +#footer a:focus { + text-decoration: underline; +} + +.footerTextColour { + color: #b3b3b3; +} + +.poweredBy { + font-size: 0.6em; +} + +/* ------------------------------- */ +/* Main Navigation Style */ +/* ------------------------------- */ + +#main-navigation { + clear: both; + background: #436783 url(images/main-nav.gif) top left repeat-x; + color: #fff; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 1em; + height: 53px; + overflow: auto; + border-bottom: 0.3em solid #47637c; + padding: 0.2em 0 0.1em 0; + margin-left: 80px +} + +#main-navigation ul li { + float: left; + display: inline; + height: 4.85em; + padding: 0 1em 0 0.5em; + border-left: 0.1em solid #5c809c; + border-right: 0.1em solid #3e5a71; +} + +#main-navigation ul .disabled { + color: #D4D0C8; +} + +#main-navigation ul .first { + border-left: none; + margin : 0px; +} + +#main-navigation ul .last { + border-right: none; +} + +#main-navigation ul a { + color: #fff; + display: block; + padding: 0 0.6em 0 0.6em; + font-size: 0.54em; + font-weight: normal; + text-transform: uppercase; + /* Uncomment next line for main-nav drop shadows */ + /*text-shadow: 0px 1px 1px #22323f;*/ +} + +#main-navigation ul a:hover, +#main-navigation ul .selected { + color: #F9924B; + text-decoration: none; +} + +#main-navigation ul a:focus { + text-decoration: underline; +} + diff --git a/themefrontjs/webapp/thfrontjs/stylertl.css b/themefrontjs/webapp/thfrontjs/stylertl.css new file mode 100644 index 000000000..85796d3a9 --- /dev/null +++ b/themefrontjs/webapp/thfrontjs/stylertl.css @@ -0,0 +1,402 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* This CSS file is used to reverse the style direction for rtl languages. */ +/* It is intended to be cascaded with style.css. */ + +/* ===== Resets/Browser Normalization ===== */ + +body, caption, th { + text-align: right; +} + +/* ===== Legacy Styles ===== */ + +.boxlink { + float: left; +} + +DIV.boxhead-left { + float: right; +} + +DIV.boxhead-right { + float: left; +} + +.submenutextinfo { + border-right: none; + border-left: 0.1em solid #5886C6; +} + +.submenutext, A.submenutext, A.submenutext:visited, +.submenutextdisabled, A.submenutextdisabled { + border-right: none; + border-left: 0.1em solid #5886C6; + padding-left: 0.5em; + padding-right: 0.2em; +} + +.submenutextinforight, .submenutextright, A.submenutextright, +.submenutextrightdisabled, A.submenutextrightdisabled { + padding-left: none; + padding-right: 0.2em; +} + +/* ===== Product Summary Styles ===== */ +.product-prevnext { + text-align: left; +} + +.productsummary .smallimage { + float: right; + margin: 0 0 0.5em 0.5em; +} + +.productsummary .productbuy { + float: left; + text-align: left; +} + +.productsummary .productinfo { + text-align: right; +} + +.treeWrapper { + border-left: none; + border-right: 0.1em solid #000000; + margin-left: 0; + margin-right: 1.2em; + text-align: right; +} + +.lefthalf { + float: right; + left: auto; + right: 0; + margin: 0 0 1% 1%; +} + +.righthalf { + float: left; + margin: 0 1% 1% 0; + right: auto; + left: 0; +} + +.leftclear { + clear: right; +} + +/* ==== Styles Copied From tabstyles.css === */ + +.treewrapper { + margin-left: 0; + margin-right: 1em; +} + +DIV.col { + float: right; +} + +DIV.col-right { + float: left; +} + +DIV.simple-right-small, DIV.simple-right-half { + float: left; + text-align: left; +} + +#column-container .left { + float: right; + margin-right: 0; + margin-left: 1em; +} + +.left-border { + float: right; + margin-right: 0; + margin-left: 1em; + border-right: none; + border-left: 0.5px dotted #ccc; +} + +#column-container .right { + float: left; + margin-left: 0; + margin-right: 1em; +} + +#column-container .leftonly { + margin-left: 0; + margin-right: 23em; +} + +#column-container .rightonly { + margin-right: 0; + margin-left: 23em; +} + +DIV.column-left-wide { + float: right; +} + +/* ===== New Styles ===== */ + +.label { + padding-right: 0; + padding-left: 1.1em; + text-align: left; +} + +/* ================================== */ +/* ===== Used to align elements ===== */ +/* ================================== */ + +.align-float { + float: left; +} + +.align-text { + text-align: left; +} + +/* ==================================== */ +/* ===== Masthead (Header) Styles ===== */ +/* ==================================== */ + +#masthead ul li { + float: right; +} + +#masthead ul .opposed, +#masthead ul .control-area { + float: left; + text-align: left; +} + +/* ---------------------- */ +/* Footer Style */ +/* ---------------------- */ + +#footer { + padding: 0.5em 0.5em 0.5em 0; +} + +#footer ul { + float: right; +} + +#footer ul li { + float: right; +} + +#footer ul .first { + border-right: none; + border-left: 0.1em solid #444; +} + +#footer ul .last { + border-left: none; + border-right: 0.1em solid #222; +} + +#footer p { + clear: right; +} + +/* ================================================== */ +/* ===== Main Navigation (App Menu) Styles ========== */ +/* ================================================== */ + +#main-navigation ul li { + float: right; + padding: 0 0.5em 0 1em; +} + +#main-navigation ul .first { + border-right: none; + border-left: 0.1em solid #5c809c; +} + +#main-navigation ul .last { + border-left: none; + border-right: 0.1em solid #3e5a71; +} + +/* ========================================================= */ +/* ===== Component Navigation (App Header) Styles ========== */ +/* ========================================================= */ + +#app-navigation ul li ul li { + float: right; + margin: 0.1em 0 0.1em 0.3em; +} + +/* ================================================= */ +/* ===== Screenlet Title Bar/Navigation Styles ===== */ +/* ================================================= */ + +.screenlet-title-bar ul li { + border-left: none; + border-right: 0.1em solid #dedede; + float: left; +} + +.screenlet-title-bar ul .h1, +.screenlet-title-bar ul .h2, +.screenlet-title-bar ul .h3 { + border-right: none; + float: right; +} + +/* login screenlet decorator */ +.login-screenlet { + margin-left: 0; + margin-right: 38%; +} + +/* ======================================================= */ +/* ===== Basic Navigation (Vertical Menu) Style ========== */ +/* ======================================================= */ + +.basic-nav { + padding-left: 0; + padding-right: 1em; +} + +/* ======================================== */ +/* ===== Button Bar Navigation Styles ===== */ +/* ======================================== */ + +.button-bar ul li { + float: right; +} + +.button-bar ul .opposed { + float: left; +} + +/* ===== Button bar decorators ===== */ + +.tab-bar ul li ul li { + float: right; + margin-right: 0; + margin-left: 0.5em; +} + +/* ======================== */ +/* ===== Table Styles ===== */ +/* ======================== */ + +.basic-table tr .label, +.basic-table tr .group-label { + /* field labels for forms */ + text-align: left; + padding-right: 0; + padding-left: 1.5em; +} + +.basic-table tr .group-label { + padding: 2em 0 0 1.5em; +} + +.basic-table tr th, +.basic-table .header-row { + text-align: right; +} + +.basic-table tr .button-col a { + float: right; +} + +/* ======================= */ +/* ===== Tree Styles ===== */ +/* ======================= */ + +.basic-tree ul, .basic-tree li { + padding-left: 0; + padding-right: 1em; +} + +.basic-tree li .expanded { + background: url(/images/collapse.gif) no-repeat right center; + padding-right: 0; + padding-left: 1em; +} + +.basic-tree li .collapsed { + background: url(/images/expand.gif) no-repeat right center; + padding-right: 0; + padding-left: 1em; +} + +/* ======================== */ +/* ===== Form Styles ===== */ +/* ======================== */ + +.basic-form table tr .label, +.basic-form table tr .group-label { + text-align: left; + padding-right: 0; + padding-left: 1.5em; +} + +.basic-form table tr .group-label { + /* "header" for field label groups */ + font-size: 1.2em; + padding: 2em 0 0 1.5em; +} + +.basic-form table tr th, +.basic-form table .header-row { + text-align: right; +} + +/* ------------------------------- */ +/* List Navigation Style */ +/* ------------------------------- */ + +.nav-pager ul li { + float: right; +} + +.nav-pager ul .nav-displaying { + border-left: none; + border-right: solid #222 0.1em; +} + +/* From http://jqueryui.com/demos/autocomplete/maxheight.html */ +/* When displaying a long list of options, you can simply set the max-height for the autocomplete menu to prevent the menu from growing too large */ +.ui-autocomplete { + max-height: 250px; + overflow-y: auto; + /* prevent horizontal scrollbar */ + overflow-x: hidden; + /* add padding to account for vertical scrollbar */ + padding-right: 20px; +} + +/* IE 6 doesn't support max-height + * we use height instead, but this forces the menu to always be this tall + */ +* html .ui-autocomplete { + height: 250px; +} diff --git a/themefrontjs/widget/CommonScreens.xml b/themefrontjs/widget/CommonScreens.xml new file mode 100644 index 000000000..7c8a5755e --- /dev/null +++ b/themefrontjs/widget/CommonScreens.xml @@ -0,0 +1,47 @@ + + + + + + + +

      + + + + + + +
      + + + + +
      + + + +
      +
      +
      +
      + + diff --git a/themefrontjs/widget/Theme.xml b/themefrontjs/widget/Theme.xml new file mode 100644 index 000000000..77d4e4ade --- /dev/null +++ b/themefrontjs/widget/Theme.xml @@ -0,0 +1,48 @@ + + + + + + + FrontJs Theme + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f84d9da49dd5e5061beabd4fd6d0e09b180106cf Mon Sep 17 00:00:00 2001 From: holivier Date: Sat, 6 Jun 2020 16:45:40 +0200 Subject: [PATCH 05/24] partyfjs plugin initialisation, copy from ofbizextra partymgrfjs plugin --- partyfjs/LICENSE | 224 +++ partyfjs/README.md | 38 + partyfjs/build.gradle | 22 + partyfjs/config/PartyFjsUiLabels.xml | 154 ++ partyfjs/data/PartyPortalSeedData.xml | 48 + partyfjs/data/PartyPortletData.xml | 297 ++++ partyfjs/entitydef/entitymodel.xml | 72 + .../FindContactMechPurposeType.groovy | 55 + .../FormatCreditCardNumber.groovy | 34 + partyfjs/groovyScripts/GetContactMechs.groovy | 59 + .../GetPartyPaymentMethods.groovy | 196 +++ .../PartyContactMechDefaultParam.groovy | 30 + .../groovyScripts/PartyRelationships.groovy | 73 + .../getParentRoleTypeDescription.groovy | 37 + partyfjs/ofbiz-component.xml | 59 + .../config/PartyEntityLabels.xml.0.dop.patch | 47 + partyfjs/servicedef/services.xml | 40 + .../_include/FR/HELP_ContactMechMgmt.adoc | 30 + .../asciidoc/_include/FR/HELP_PartyInfo.adoc | 19 + .../_include/FR/HELP_PartyRelationFroms.adoc | 45 + .../_include/FR/HELP_PartyRelationTos.adoc | 45 + .../asciidoc/_include/FR/HELP_PartyRoles.adoc | 25 + .../_include/FR/HELP_PartyUserLogins.adoc | 29 + .../_include/FR/HELP_SelectParty.adoc | 16 + .../_include/profile/ContactMechMgmt.adoc | 48 + .../asciidoc/_include/profile/PartyInfo.adoc | 30 + .../_include/profile/PartyRelationFroms.adoc | 64 + .../_include/profile/PartyRelationTos.adoc | 63 + .../asciidoc/_include/profile/PartyRoles.adoc | 35 + .../_include/profile/PartyUserLogins.adoc | 44 + .../_include/profile/SelectParty.adoc | 28 + .../asciidoc/screenlet-party-profile_fr.adoc | 409 +++++ .../src/docs/asciidoc/screenlet-party.adoc | 35 + partyfjs/template/editbillingaccountterm.ftl | 122 ++ .../webapp/partymgrfjs/WEB-INF/controller.xml | 496 ++++++ partyfjs/webapp/partymgrfjs/WEB-INF/web.xml | 93 ++ partyfjs/webapp/partymgrfjs/error/error.jsp | 34 + partyfjs/webapp/partymgrfjs/index.jsp | 1 + partyfjs/widget/CommonScreens.xml | 123 ++ partyfjs/widget/PartyFjsForms.xml | 468 ++++++ partyfjs/widget/PartyFjsMenus.xml | 46 + partyfjs/widget/PartyFjsScreens.xml | 343 +++++ partyfjs/widget/PartyForms.xml | 51 + partyfjs/widget/ProfileFjsForms.xml | 1369 +++++++++++++++++ partyfjs/widget/ProfileFjsMenus.xml | 587 +++++++ partyfjs/widget/ProfileFjsScreens.xml | 1172 ++++++++++++++ partyfjs/widget/VisitFjsForms.xml | 157 ++ partyfjs/widget/VisitFjsScreens.xml | 174 +++ 48 files changed, 7686 insertions(+) create mode 100644 partyfjs/LICENSE create mode 100644 partyfjs/README.md create mode 100644 partyfjs/build.gradle create mode 100755 partyfjs/config/PartyFjsUiLabels.xml create mode 100644 partyfjs/data/PartyPortalSeedData.xml create mode 100644 partyfjs/data/PartyPortletData.xml create mode 100644 partyfjs/entitydef/entitymodel.xml create mode 100644 partyfjs/groovyScripts/FindContactMechPurposeType.groovy create mode 100755 partyfjs/groovyScripts/FormatCreditCardNumber.groovy create mode 100644 partyfjs/groovyScripts/GetContactMechs.groovy create mode 100755 partyfjs/groovyScripts/GetPartyPaymentMethods.groovy create mode 100644 partyfjs/groovyScripts/PartyContactMechDefaultParam.groovy create mode 100644 partyfjs/groovyScripts/PartyRelationships.groovy create mode 100644 partyfjs/groovyScripts/getParentRoleTypeDescription.groovy create mode 100644 partyfjs/ofbiz-component.xml create mode 100755 partyfjs/ofbizfile/applications/party/config/PartyEntityLabels.xml.0.dop.patch create mode 100644 partyfjs/servicedef/services.xml create mode 100644 partyfjs/src/docs/asciidoc/_include/FR/HELP_ContactMechMgmt.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyInfo.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRelationFroms.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRelationTos.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRoles.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyUserLogins.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/FR/HELP_SelectParty.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/profile/ContactMechMgmt.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/profile/PartyInfo.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/profile/PartyRelationFroms.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/profile/PartyRelationTos.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/profile/PartyRoles.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/profile/PartyUserLogins.adoc create mode 100644 partyfjs/src/docs/asciidoc/_include/profile/SelectParty.adoc create mode 100644 partyfjs/src/docs/asciidoc/screenlet-party-profile_fr.adoc create mode 100644 partyfjs/src/docs/asciidoc/screenlet-party.adoc create mode 100755 partyfjs/template/editbillingaccountterm.ftl create mode 100644 partyfjs/webapp/partymgrfjs/WEB-INF/controller.xml create mode 100644 partyfjs/webapp/partymgrfjs/WEB-INF/web.xml create mode 100644 partyfjs/webapp/partymgrfjs/error/error.jsp create mode 100644 partyfjs/webapp/partymgrfjs/index.jsp create mode 100644 partyfjs/widget/CommonScreens.xml create mode 100644 partyfjs/widget/PartyFjsForms.xml create mode 100644 partyfjs/widget/PartyFjsMenus.xml create mode 100644 partyfjs/widget/PartyFjsScreens.xml create mode 100644 partyfjs/widget/PartyForms.xml create mode 100644 partyfjs/widget/ProfileFjsForms.xml create mode 100644 partyfjs/widget/ProfileFjsMenus.xml create mode 100644 partyfjs/widget/ProfileFjsScreens.xml create mode 100755 partyfjs/widget/VisitFjsForms.xml create mode 100755 partyfjs/widget/VisitFjsScreens.xml diff --git a/partyfjs/LICENSE b/partyfjs/LICENSE new file mode 100644 index 000000000..f88e5a12b --- /dev/null +++ b/partyfjs/LICENSE @@ -0,0 +1,224 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +========================================================================= + +This product bundles "Noto Sans" fonts, which are available under the +"Apache License Version 2.0". +For details, see framework/resources/fonts/NotoSans/. + +========================================================================= + +This product bundles the "Quicksand" fonts, which is available +under the "SIL OFL License 1.1" license. +For details, see themes/rainbowstone/webapp/rainbowstone/fonts/quicksand + +========================================================================= + +This product bundles "timezones" files, which are available in the "Public Domain". +For details, see themes/common/webapp/common/js/plugins/date/timezones. + +========================================================================= + +This product bundles "Date Format 1.2.3", which is available +under the "MIT" license. +For details, see themes/common/webapp/common/js/plugins/date/date.format-1.2.3* diff --git a/partyfjs/README.md b/partyfjs/README.md new file mode 100644 index 000000000..5a3b1838a --- /dev/null +++ b/partyfjs/README.md @@ -0,0 +1,38 @@ +partyFjs plugin +================== +(partymgr for FrontJs renderer) + +This plugin is a Apache OFBiz plugin, part of a group of plugin which intended to enhance OFBiz to work +with the vue.js SPA (Single Page Application). + +Group of plugin contain: +- vuejsPortal as the "framework" part +- examplefjs as example of usage +- flatgreyfjs as a dedicated theme to load only javascript needed (almost nothing) + +The 3 are in the ofbizextra/ofbizplugins/ gitlab repository + +partyFjs is the fourth plugin, it's the first ofbiz component migrate, currently it's at a WorkInProgress status + +FrontJs-Vue.js, is a project which is, at the moment, at the Proof of Concept level. So sometine it's speaking about vue.js, sometime about frontjs +and sometimes about Portal sometine screen. + +It's goal is to be a base to a future Apache OFBiz plugin integrated to Apache OFBiz. + +The latest about this OFBiz plugin can be found in the documentation (see below) + +# Implementation +Have a look to install documentation https://ofbizextra.org/ofbizextra_adocs/docs/asciidoc/developer-manual.html#_poc_vuejsportal_installation + +## Summary For developers +1. clone the repo in the plugins folder of your OFBiz location for the 3 plugins +2. apply the ofbiz patch (new tag/attributes in xsd) (from ofbizFiles vuejsPortal directory) +3. apply patch about Jira waiting validation ((from ofbizCommit2add vuejsPortal directory) +4. copy new files in ofbiz (from ofbizFiles vuejsPortal directory) +5. clone the repo in the plugins folder of your OFBiz location +6. restart your OFBiz implementation +7. load data sets of the plugin via webtools/import (for the portalPage) + + +# Documentation +All documentation and detail about this plugin is on https://ofbizextra.org/ofbizextra_adocs/docs/asciidoc/developer-manual.html#_frontjs_portal \ No newline at end of file diff --git a/partyfjs/build.gradle b/partyfjs/build.gradle new file mode 100644 index 000000000..23cd23f1b --- /dev/null +++ b/partyfjs/build.gradle @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +dependencies { + pluginLibsCompile 'org.apache.tomcat.embed:tomcat-embed-websocket:9.0.19' +} diff --git a/partyfjs/config/PartyFjsUiLabels.xml b/partyfjs/config/PartyFjsUiLabels.xml new file mode 100755 index 000000000..c2c663738 --- /dev/null +++ b/partyfjs/config/PartyFjsUiLabels.xml @@ -0,0 +1,154 @@ + + + + + + خيارات البحث + Možnosti vyhledávání + Suchoptionen + Search Options + Opciones de búsqueda + Opciones de Búsqueda + Critères de recherche + खोजने के लिए विकल्प + Opzioni di ricerca + 検索オプション + Zoekmogelijkheden + Opções de pesquisa + Опции поиска + Tuỳ chọn tìm kiếm + 搜索选项 + 搜尋選項 + + + + Contact Name + Nom du contact + + + Parent Party Classification Type Id + Classification d'acteur parent + + + Name + Nom + + + Role Type Group From + Groupe de Rôle d'origine + + + Role Type Group To + Groupe de Rôle de destination + + + Party List + Liste des acteurs + + + Party Lists to which it is associated + Liste des acteurs auxquels il est associés + + + Benutzername(n) + UserLogins management + Usuario(s) + Gestion des identifiants de connexion + उपयोगकर्ता नाम(ओं) + Nome Utente(i) + Gebruikersnamen + Nome(s) de usuário + Utilizador(es) + Nume Utilizator(i) + Имя пользователя (ей) + ชื่อผู้ใช้ + 用户名 + 使用者名稱 + + + Select one Party + Sélection d'un acteur + + + Parent Role Type Id + Rôle Père + + + Select the Parent Role + Sélectionnez le rôle père + + + Add a phone number + Ajouter un fax + + + Add an Email address + Ajouter une Adresse mail + + + Add a phone number + Ajouter un portable + + + Add a postal Address + Ajouter une Adresse postale + + + Add a phone number + Ajouter un num.tel. + + + Lookup Party by role + Recherche d'acteurs selon le rôle + + + Lookup Party by role group + Recherche d'acteurs selon le groupe de rôle + + + Lookup Party roles + Recherche de rôles d'un acteur + + + Liste des fournisseurs + Suppliers list + + + Liste des consommateurs + Consumer list + + + Select one customer + Sélectionner un consommateur + + + Select one supplier + Sélectionner un fournisseur + + + BIC + BIC + + + IBAN + IBAN + + \ No newline at end of file diff --git a/partyfjs/data/PartyPortalSeedData.xml b/partyfjs/data/PartyPortalSeedData.xml new file mode 100644 index 000000000..22e12403d --- /dev/null +++ b/partyfjs/data/PartyPortalSeedData.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/partyfjs/data/PartyPortletData.xml b/partyfjs/data/PartyPortletData.xml new file mode 100644 index 000000000..b1dd257b1 --- /dev/null +++ b/partyfjs/data/PartyPortletData.xml @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/partyfjs/entitydef/entitymodel.xml b/partyfjs/entitydef/entitymodel.xml new file mode 100644 index 000000000..f02468e49 --- /dev/null +++ b/partyfjs/entitydef/entitymodel.xml @@ -0,0 +1,72 @@ + + + + + + + + Entity of an Apache OFBiz Component + Extend for PartyFjs + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/partyfjs/groovyScripts/FindContactMechPurposeType.groovy b/partyfjs/groovyScripts/FindContactMechPurposeType.groovy new file mode 100644 index 000000000..eb5b8e23f --- /dev/null +++ b/partyfjs/groovyScripts/FindContactMechPurposeType.groovy @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.ofbiz.entity.condition.EntityCondition +import org.apache.ofbiz.entity.condition.EntityFieldValue +import org.apache.ofbiz.entity.condition.EntityFunction +import org.apache.ofbiz.entity.condition.EntityOperator +import org.apache.ofbiz.base.util.UtilProperties +import org.apache.ofbiz.base.util.UtilMisc +import org.apache.ofbiz.entity.GenericValue +import org.apache.ofbiz.base.util.Debug + + +delegator = request.getAttribute("delegator") +entityLabelMap = UtilProperties.getResourceBundleMap("PartyEntityLabels", locale) + + +andExprs = [] +//Debug.logInfo("DEBUG request: ${request}", "FindContactMechPuroseType.groovy") +fieldValue = request.getAttribute("contactMechTypeId") +if (! fieldValue) fieldValue = request.getParameter("contactMechTypeId") +//Debug.logInfo("DEBUG fieldValue: ${fieldValue}", "FindContactMechPuroseType.groovy") +if (fieldValue) { + andExprs.add(EntityCondition.makeCondition(EntityFunction.UPPER(EntityFieldValue.makeFieldValue("contactMechTypeId")), + EntityOperator.EQUALS, fieldValue.toUpperCase())) +} +//Debug.logInfo("DEBUG andExprs: ${andExprs}", "FindContactMechPuroseType.groovy") +purposeTypeIdList = [] +if (andExprs) { + purposeTypeIdList = select("contactMechPurposeTypeId").from("ContactMechTypePurpose").where(andExprs).queryList() + purposeTypeList = [] + for (GenericValue purposeTypeGV : purposeTypeIdList) { + purposeTypeList.add(UtilMisc.toMap("contactMechPurposeTypeId", purposeTypeGV.contactMechPurposeTypeId, + "description", entityLabelMap["ContactMechPurposeType.description." + purposeTypeGV.contactMechPurposeTypeId])) + } + //Debug.logInfo("purposeTypeList: ${purposeTypeList}", "FindContactMechPuroseType.groovy") + request.setAttribute("purposeTypeList", UtilMisc.sortMaps(purposeTypeList, UtilMisc.toList("description"))) +} +return "success" diff --git a/partyfjs/groovyScripts/FormatCreditCardNumber.groovy b/partyfjs/groovyScripts/FormatCreditCardNumber.groovy new file mode 100755 index 000000000..acd3b0ec8 --- /dev/null +++ b/partyfjs/groovyScripts/FormatCreditCardNumber.groovy @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.ofbiz.base.util.UtilValidate; + +//Display only last four digits from card number +if (UtilValidate.isNotEmpty(creditCard) && UtilValidate.isNotEmpty(creditCard.cardNumber)) { + cardNumberDisplay = "" + cardNumber = creditCard.cardNumber + size = cardNumber.size() - 4 + if (size >0) { + for (int i = 0; i < size; i++) { + cardNumberDisplay += "*" + } + cardNumberDisplay += cardNumber[size .. size + 3] + context.cardNumber = cardNumberDisplay + } +} \ No newline at end of file diff --git a/partyfjs/groovyScripts/GetContactMechs.groovy b/partyfjs/groovyScripts/GetContactMechs.groovy new file mode 100644 index 000000000..36bc1eab2 --- /dev/null +++ b/partyfjs/groovyScripts/GetContactMechs.groovy @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.text.DateFormat + +import org.apache.ofbiz.base.util.Debug +import org.apache.ofbiz.base.util.UtilDateTime +import org.apache.ofbiz.entity.GenericValue; +import org.apache.ofbiz.party.contact.ContactMechWorker + +DateFormat df = UtilDateTime.toDateTimeFormat(UtilDateTime.getDateTimeFormat(), timeZone, null); + +partyId = request.getAttribute("partyId") +if (! partyId) partyId = request.getParameter("partyId") + +showOldStr = request.getAttribute("showOld") +if (! showOldStr) showOldStr = request.getParameter("showOld") +showOld = "true".equals(showOldStr) + +contactMeches = ContactMechWorker.getPartyContactMechValueMaps(delegator, partyId, showOld) + +for (Map contactMeche: contactMeches) { + gvCM = (GenericValue)contactMeche.get("partyContactMech") + Map partyContactMech = gvCM.getAllFields() + + partyContactMech.put("fromDate", df.format((java.util.Date) gvCM.getTimestamp("fromDate"))) + if (gvCM.getTimestamp("thruDate")) + partyContactMech.put("thruDate", df.format((java.util.Date) gvCM.getTimestamp("thruDate"))) + contactMeche.put("partyContactMech", partyContactMech) + + gvCMPList = (List)contactMeche.get("partyContactMechPurposes") + if (gvCMPList) { + for (int i = 0; i < gvCMPList.size(); i++) { + gvCMP = (GenericValue)gvCMPList.get(i) + Map partyContactMechPurpose = gvCMP.getAllFields() + partyContactMechPurpose.put("fromDate", df.format((java.util.Date) gvCMP.getTimestamp("fromDate"))) + if (gvCMP.getTimestamp("thruDate")) + partyContactMechPurpose.put("thruDate", df.format((java.util.Date) gvCMP.getTimestamp("thruDate"))) + gvCMPList.set(i,partyContactMechPurpose) + } + } +} +request.setAttribute("valueMaps", contactMeches) diff --git a/partyfjs/groovyScripts/GetPartyPaymentMethods.groovy b/partyfjs/groovyScripts/GetPartyPaymentMethods.groovy new file mode 100755 index 000000000..8956766f5 --- /dev/null +++ b/partyfjs/groovyScripts/GetPartyPaymentMethods.groovy @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.ofbiz.accounting.payment.PaymentWorker +import org.apache.ofbiz.accounting.payment.BillingAccountWorker +import org.apache.ofbiz.party.contact.ContactHelper +import org.apache.ofbiz.base.util.UtilMisc +import org.apache.ofbiz.base.util.UtilValidate +import org.apache.ofbiz.base.util.Debug +import org.apache.ofbiz.entity.condition.* +import org.apache.ofbiz.entity.GenericValue +import org.apache.ofbiz.entity.util.EntityUtil + + +partyId = parameters.partyId ?: userLogin.partyId +showOld = "true".equals(parameters.SHOW_OLD) + +currencyUomId = null; +paymentInfos = [] +paymentInfoList = [] +if (partyId) { + //Retrieve Billing Account + billingAccountAndRoles = from("BillingAccountAndRole").where("partyId", partyId, "thruDate", null).queryList() + for (billingAccountAndRole in billingAccountAndRoles) { + Debug.logInfo(billingAccountAndRole.billingAccountId, "GetPartyPaymentMethods") + currencyUomId = billingAccountAndRole.accountCurrencyUomId + if (currencyUomId) billingAccountList = BillingAccountWorker.makePartyBillingAccountList(userLogin, currencyUomId, partyId, delegator, dispatcher) + for (billingAccount in billingAccountList) { + Debug.logInfo(billingAccount.toString(), "GetPartyPaymentMethods") + if (paymentInfoList.contains(billingAccount) == false) { + paymentInfoList.addAll(billingAccount) + } + } + } + if (showOld) { + EntityCondition cond = EntityCondition.makeCondition( + [EntityCondition.makeCondition("partyId", EntityOperator.EQUALS, partyId), + EntityCondition.makeCondition("accountThruDate", EntityOperator.NOT_EQUAL, null) + ], EntityOperator.AND) + billingAccountExpired = delegator.findList("BillingAccountAndRole", cond, null, null, null, false) + for (billingAccountAR in billingAccountExpired) { + billingAccountGV = delegator.findOne("BillingAccount",[billingAccountId : billingAccountAR.billingAccountId] ,true) + billingAccount = [:] + billingAccount.putAll(billingAccountGV) + if (paymentInfoList.contains(billingAccount) == false) { + paymentInfoList.addAll(billingAccount) + } + } + } + + //Add payment methods + paymentMethodList = PaymentWorker.getPartyPaymentMethodValueMaps(delegator, partyId, showOld) + if (UtilValidate.isNotEmpty(paymentMethodList)) { + paymentInfoList.addAll(paymentMethodList) + } + + //Then create a description string for each information + for (paymentInfo in paymentInfoList) { + value = [:] + //Billing Account + if (UtilValidate.isNotEmpty(paymentInfo.billingAccountId)) { + //Retrieve uom abbr + uomAbbr = "" + if (paymentInfo.accountCurrencyUomId) { + GenericValue uom = from("Uom").where("uomId",paymentInfo.accountCurrencyUomId).cache(true).queryOne() + if (UtilValidate.isNotEmpty(uom)) { + uomAbbr = uom.get("abbreviation",locale) + } + } + + //Create description for display + Debug.logInfo(paymentInfo.toString(), "GetPartyPaymentMethods3") + description = paymentInfo.billingAccountId + if (paymentInfo.description) description += " (" + paymentInfo.description+")" + if (paymentInfo.accountLimit) description += " (" + uiLabelMap.AccountingAccountLimit + " "+paymentInfo.accountLimit+" "+uomAbbr+")" + if (paymentInfo.accountBalance) description += " (" + uiLabelMap.AccountingBillingAvailableBalance +" "+ paymentInfo.accountBalance+" "+uomAbbr+")" + if (paymentInfo.fromDate) description += " (" + uiLabelMap.CommonUpdated +" : "+ paymentInfo.fromDate+")" + if (paymentInfo.thruDate) description += " (" + uiLabelMap.PartyContactEffectiveThru +" : " +paymentInfo.thruDate+")" + + value = UtilMisc.toMap("billingAccountId",paymentInfo.billingAccountId,"type", uiLabelMap.AccountingBilling,"description",description) + Debug.logInfo(value.toString(), "GetPartyPaymentMethods4") + + } else if (UtilValidate.isNotEmpty(paymentInfo.paymentMethod)) { + paymentMethod = paymentInfo.paymentMethod + + //Retrieve payment method type + type = paymentMethod.paymentMethodTypeId + paymentMethodType = paymentMethod.getRelatedOne("PaymentMethodType", true) + if (UtilValidate.isNotEmpty(paymentMethodType)) { + type = paymentMethodType.get("description",locale) + } + + //Create description for display + description = "" + + if ("CREDIT_CARD".equals(paymentMethod.paymentMethodTypeId)) { + //Credit Card + creditCard = paymentMethod.getRelatedOne("CreditCard", true) + if (UtilValidate.isNotEmpty(creditCard)) { + if (creditCard.companyNameOnCard) description += " "+ creditCard.companyNameOnCard + if (creditCard.titleOnCard) description += " - "+ creditCard.titleOnCard + if (creditCard.firstNameOnCard) description += " " + creditCard.firstNameOnCard + if (creditCard.middleNameOnCard) description += " " + creditCard.middleNameOnCard + if (creditCard.suffixOnCard) description += " " + creditCard.suffixOnCard + if (security.hasEntityPermission("PAY_INFO", "_VIEW", session)) { + if (creditCard.cardType) description += " " + creditCard.cardType + cardNumber = creditCard.cardNumber + if (UtilValidate.isNotEmpty(cardNumber)) { + cardNumberDisplay = "" + size = cardNumber.size() - 4 + if (size >0) { + for (int i = 0; i < size-1; i++) { + cardNumberDisplay += "*" + } + cardNumberDisplay += cardNumber[size .. size + 3] + description += " " + cardNumberDisplay + } + } + if (creditCard.expireDate) description += " " + creditCard.expireDate + } else { + description += ContactHelper.formatCreditCard(creditCard) + } + } + + } else if ("GIFT_CARD".equals(paymentMethod.paymentMethodTypeId)) { + //Gift Card + giftCard = paymentMethod.getRelatedOne("GiftCard", true) + if (UtilValidate.isNotEmpty(giftCard)) { + if (security.hasEntityPermission("PAY_INFO", "_VIEW", session)) { + cardNumber = giftCard.cardNumber + pinNumber = giftCard.pinNumber + if (UtilValidate.isEmpty(cardNumber)) cardNumber = "N/A" + if (UtilValidate.isEmpty(pinNumber)) pinNumber = "N/A" + + description += " "+ cardNumber + " [" + pinNumber +"]" + } else { + //Hide card number + if (UtilValidate.isNotEmpty(giftCard.cardNumber)) { + cardNumberDisplay = "" + cardNumber = giftCard.cardNumber + size = cardNumber.size() - 4 + if (size >0) { + for (int i = 0; i < size-1; i++) { + cardNumberDisplay += "*" + } + cardNumberDisplay += cardNumber[size .. size + 3] + description += cardNumberDisplay + } + } else { + description += "N/A" + } + } + } + + } else if ("EFT_ACCOUNT".equals(paymentMethod.paymentMethodTypeId)) { + //Eft Account + eftAccount = paymentMethod.getRelatedOne("EftAccount", true) + if (UtilValidate.isNotEmpty(eftAccount)) { + if (eftAccount.nameOnAccount) description += " "+ eftAccount.nameOnAccount + if (eftAccount.bankName) description += " - "+ uiLabelMap.PartyBank +" : "+ eftAccount.bankName + if (eftAccount.routingNumber && eftAccount.accountNumber) description += " "+ uiLabelMap.PartyAccount +" : BIC : "+ eftAccount.routingNumber+", IBAN :"+ eftAccount.accountNumber + else if (eftAccount.accountNumber) description += " "+ uiLabelMap.PartyAccount +" : "+ eftAccount.accountNumber + } + } + if (paymentMethod.description) description += " ("+ paymentMethod.description +")" + if (paymentMethod.glAccountId) description += " ("+ uiLabelMap.CommonFor +" "+ paymentMethod.glAccountId+")" + if (paymentMethod.fromDate) description += " ("+ uiLabelMap.CommonUpdated +" : "+ paymentMethod.fromDate+")" + if (paymentMethod.thruDate) { + description += " ("+ uiLabelMap.PartyContactEffectiveThru +" : "+ paymentMethod.thruDate+")" + value = UtilMisc.toMap("paymentMethodId",paymentMethod.paymentMethodId,"type", type,"description", description) + } else value = UtilMisc.toMap("paymentMethodId",paymentMethod.paymentMethodId,"type", type,"description", description, "activeEdit", "Y") + } + paymentInfos.add(value) + } +} +Debug.logInfo(paymentInfos.toString(), "GetPartyPaymentMethodsFINAL") + +context.paymentInfos = paymentInfos +context.showOld = showOld +context.partyId = partyId diff --git a/partyfjs/groovyScripts/PartyContactMechDefaultParam.groovy b/partyfjs/groovyScripts/PartyContactMechDefaultParam.groovy new file mode 100644 index 000000000..10fff7888 --- /dev/null +++ b/partyfjs/groovyScripts/PartyContactMechDefaultParam.groovy @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +context.displayParams = context.displayParams ? context.displayParams : [:] +context.displayParams.POSTAL_ADDRESS = context.displayParams.POSTAL_ADDRESS ? context.displayParams.POSTAL_ADDRESS : context.postalAddr +context.displayParams.EMAIL_ADDRESS = context.displayParams.EMAIL_ADDRESS ? context.displayParams.EMAIL_ADDRESS : context.email +context.displayParams.TELECOM_NUMBER = context.displayParams.TELECOM_NUMBER ? context.displayParams.TELECOM_NUMBER : context.telecom +context.displayParams.IP_ADDRESS = context.displayParams.IP_ADDRESS ? context.displayParams.IP_ADDRESS : context.none +context.displayParams.DOMAIN_NAME = context.displayParams.DOMAIN_NAME ? context.displayParams.DOMAIN_NAME : context.none +context.displayParams.WEB_ADDRESS = context.displayParams.WEB_ADDRESS ? context.displayParams.WEB_ADDRESS : context.none +context.displayParams.INTERNAL_PARTYID = context.displayParams.INTERNAL_PARTYID ? context.displayParams.INTERNAL_PARTYID : context.none +context.displayParams.FTP_ADDRESS = context.displayParams.FTP_ADDRESS ? context.displayParams.FTP_ADDRESS : context.none +context.displayParams.LDAP_ADDRESS = context.displayParams.LDAP_ADDRESS ? context.displayParams.LDAP_ADDRESS : context.none + diff --git a/partyfjs/groovyScripts/PartyRelationships.groovy b/partyfjs/groovyScripts/PartyRelationships.groovy new file mode 100644 index 000000000..048d4b43e --- /dev/null +++ b/partyfjs/groovyScripts/PartyRelationships.groovy @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.ofbiz.base.util.UtilProperties +import org.apache.ofbiz.base.util.UtilDateTime +import org.apache.ofbiz.entity.condition.EntityCondition +import org.apache.ofbiz.entity.condition.EntityConditionBuilder + +partyId = parameters.partyId ?: parameters.party_id + +roleTypeEntity = UtilProperties.getPropertyValue('general.properties', 'entityRoleTypeGroupe', 'RoleType') // to be able to use RoleTypeGroup if it exist +context.roleTypeGroupIdFrom = context.roleTypeGroupIdFrom +def roleTypeIdFromList = [] +if (context.roleTypeGroupIdFrom) { + listGv = from(roleTypeEntity).where("parentTypeId", context.roleTypeGroupIdFrom).queryList() + listGv.each{ roleType -> roleTypeIdFromList << roleType.roleTypeId } +} + +context.roleTypeGroupIdTo = context.roleTypeGroupIdTo +def roleTypeIdToList = [] +if (context.roleTypeGroupIdTo) { + listGv = from(roleTypeEntity).where("parentTypeId", context.roleTypeGroupIdTo).queryList() + listGv.each{ roleType -> roleTypeIdToList << roleType.roleTypeId } +} + +sortField = parameters.sortField ? parameters.sortField : "fromDate" + +//Build condition +condList = [] +exprBldr = new EntityConditionBuilder() +def EntityCondition condition +if (relationIs == "FROM") condition = exprBldr.AND() { EQUALS(partyIdFrom: parameters.partyId) } +if (relationIs != "FROM") condition = exprBldr.AND() { EQUALS(partyIdTo: parameters.partyId) } + +context.partyRelationshipTypeIdAttr = context.partyRelationshipTypeId +if (context.partyRelationshipTypeIdAttr) condition = exprBldr.AND(condition) { EQUALS(partyRelationshipTypeId: context.partyRelationshipTypeIdAttr) } + +if (parameters.roleTypeIdFrom) condition = exprBldr.AND(condition) { EQUALS(roleTypeIdFrom: parameters.roleTypeIdFrom) } +if (parameters.roleTypeIdTo) condition = exprBldr.AND(condition) { EQUALS(roleTypeIdTo: parameters.roleTypeIdTo) } + +if (roleTypeIdFromList) condition = exprBldr.AND(condition) { IN(roleTypeIdFrom: roleTypeIdFromList) } +if (roleTypeIdToList) condition = exprBldr.AND(condition) { IN(roleTypeIdTo: roleTypeIdToList) } + +//condition = exprBldr.AND() condList + +if (parameters.showHistory == "Y") + context.partyRelationList = from("PartyRelationship").where(condition).orderBy(sortField).queryList() +else + context.partyRelationList = from("PartyRelationship").where(condition).orderBy(sortField).filterByDate().queryList() + +context.Y = "Y" // to simplify use-when condition writing +context.showEditButton = context.showEditButton ? context.showEditButton : "Y" +context.showDeleteButton = context.showDeleteButton ? context.showDeleteButton : "Y" +context.showHistoryButton = context.showHistoryButton ? context.showHistoryButton : "Y" +context.showHistory = parameters.showHistory ? parameters.showHistory : "N" +context.editArea = "PartyRelation" + (relationIs == "FROM" ? "From" : "To") + "s_editArea" + diff --git a/partyfjs/groovyScripts/getParentRoleTypeDescription.groovy b/partyfjs/groovyScripts/getParentRoleTypeDescription.groovy new file mode 100644 index 000000000..7131adc29 --- /dev/null +++ b/partyfjs/groovyScripts/getParentRoleTypeDescription.groovy @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.ofbiz.base.util.UtilMisc +import org.apache.ofbiz.base.util.UtilProperties +import org.apache.ofbiz.entity.condition.EntityCondition +import org.apache.ofbiz.entity.condition.EntityOperator + +//get list of roleTypeId with childs and its description + +partyId = parameters.partyId ?: parameters.party_id + +roleTypeEntity = UtilProperties.getPropertyValue('general.properties', 'entityRoleTypeGroupe', 'RoleType') // to be able to use RoleTypeGroup if it exist + +parentRoleList = select("parentTypeId").from(roleTypeEntity).where(EntityCondition.makeCondition("parentTypeId", EntityOperator.NOT_EQUAL, null)).distinct().queryList() +parentRoleList.each { parentRole -> + roleType = from("RoleType").where("roleTypeId", parentRole.parentTypeId).queryOne(); + parentRole.description = roleType.get('description',locale) +} +context.parentRoleList = UtilMisc.sortMaps(parentRoleList, UtilMisc.toList("+description")) + diff --git a/partyfjs/ofbiz-component.xml b/partyfjs/ofbiz-component.xml new file mode 100644 index 000000000..2b8175663 --- /dev/null +++ b/partyfjs/ofbiz-component.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/partyfjs/ofbizfile/applications/party/config/PartyEntityLabels.xml.0.dop.patch b/partyfjs/ofbizfile/applications/party/config/PartyEntityLabels.xml.0.dop.patch new file mode 100755 index 000000000..6af42fe4b --- /dev/null +++ b/partyfjs/ofbizfile/applications/party/config/PartyEntityLabels.xml.0.dop.patch @@ -0,0 +1,47 @@ + + + + + + Parent Classification Type use to filter showing classifiction type + Type de classification parente à utiliser pour filter les types de classification à visualiser + + + RoleType Group which will be used in add screen to show only roleType included in this roleTypeGroup. If this field is empty all role type will be . + Groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'ajout. Si ce champ est vide tous les rôles apparaitront + + + Show only Party Relationship with this partyRelationshipTypeId, or left empty if you want to show field + indiquer le type de relation à utiliser pour filtrer les relations à afficher, ou laisser le champ vide pour les afficher toutes et donc montrer ce champ + + + If attribute roleType is empty, give roleTypeGroup which will be used in edit screen (add - update) to show only roleType included in this roleTypeGroup. In this case roleTypeId will be show in the list. + Si le champ Role est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle d'origine sera alors affiché dans la liste. + + + If attribute roleType is empty, give roleTypeGroup which will be used in edit screen (add - update) to show only roleType included in this roleTypeGroup. In this case roleTypeId will be show in the list. + Si le champ Role est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle de destination sera alors affiché dans la liste. + + + If this field is fielded, its value will used to filter list, will use on edit screen (add or update), the field roleType will not be show. + Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. + + + If this field is fielded, its value will used to filter list, will use on edit screen (add or update), the field roleType will not be show. + Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. + + + role Type To + Rôle de l'acteur utiliser dans l'opération + + + Default role use to filter partyRelationship on role type id to to find parties contact + Rôle à utiliser pour filtrer les acteurs en relation avec la société + + + Default status use for the communicationEventRole + Statut par défaut utilisé par les rôles associés à la communication + + + + diff --git a/partyfjs/servicedef/services.xml b/partyfjs/servicedef/services.xml new file mode 100644 index 000000000..3391276dc --- /dev/null +++ b/partyfjs/servicedef/services.xml @@ -0,0 +1,40 @@ + + + + + Partyfjs Component Services + OFBiz + 1.0 + + + + Create UserLogin and add a SecurityGroupId to the userLogin. + securityGroupId is read directly as a portletAttribute "securityGroupId". + Tested by a JunitTest + + + + + + + + diff --git a/partyfjs/src/docs/asciidoc/_include/FR/HELP_ContactMechMgmt.adoc b/partyfjs/src/docs/asciidoc/_include/FR/HELP_ContactMechMgmt.adoc new file mode 100644 index 000000000..d45bd85b2 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/FR/HELP_ContactMechMgmt.adoc @@ -0,0 +1,30 @@ + += Portlet de gestion des coordonnées. + +Cette portlet permet d'afficher les coordonnées d'un objet metier, acteur, magasin, commande, ... + +Selon le type de coordonnée (adresse postal, mail, téléphone, ...), l'affichage et la saisie diffère. +Dans tous les cas, il est possible de préciser le but de la coordonnée (adresse de facturation, et/ou de livraison, et/ou ...) + +Une coordonnée peut être associé à un ou plusieurs objet métier, par exemple l'adresse postal de livraison d'un client sera aussi associé à ses différentes commandes ou devis. +Il est donc trés rare de supprimer une coordonnée, il est par contre, possible de l'invalider, en effet chaque coordonnée posséde des date de validité (de .. jusqu'a ..) + +Par défaut seul les coordonnées valide sont affiché et un boutton permet d'afficher l'historique + +  + +== Les paramètre de la Portlet + +* Paramétrage du champ N° de téléphone : ce champ peut être compéter par 3 sous-champ complémentaire (pay, région, ext), chacun peut ou non etre utilisé. +* Il est possible de faire apparaitre ou chacune des options du menu d'ajout +* Il est possible, pour chacun des types d'ajout de donner un but qui sera utilisé lors de la création + + +== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours (et celles du composant) + + +== Remarques Techniques + +Cette portlet fonctionne si elle reçoit un partyId, ou un facilityId ou un orderId ou un workEffortId \ No newline at end of file diff --git a/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyInfo.adoc b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyInfo.adoc new file mode 100644 index 000000000..32f1d2e06 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyInfo.adoc @@ -0,0 +1,19 @@ + += Portlet Les information d'un acteur. + +Cette portlet affiche un résumé des informations de l'acteur (différent selon le type de l'acteur Personne ou Groupe d'acteur). + +Le Bouton édition permet de voir l'ensemble des informations et au besoin de les modifier. + +  + +== Les paramètre de la Portlet + +* Le show screenlet menu Standard, il permet de cacher le menu de la portlet (refresh et édition) +* Le show edit menu Standard: s'il est positionné à N alors le boutton "édition" n'apparait pas + + + +== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours de l'acteur (et celles du composant) diff --git a/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRelationFroms.adoc b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRelationFroms.adoc new file mode 100644 index 000000000..c97297213 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRelationFroms.adoc @@ -0,0 +1,45 @@ + += Liste des acteurs associés. + +Cette portlet permet d'afficher les acteurs associés à l'acteur sélectionné. +L'affichage et son comportement dependent de son paramétrage. + +Pour caractériser la relation entre deux acteurs, il faut préciser le "type de relation", le plus courament cette portlet est utilisé pour un type de relation précis, par exemple + +* "Contact", c'est à dire lister tous les contacts d'un commerciale ou chez un client; +* "Employé", c'est à dire lister tous les employées d'une société; +* "Membre d'équipe", c'est à dire lister tous les membres d'une équipe ou d'un service; +* $$...$$ + +Pour une relation entre acteur, il est aussi possible de préciser le rôle de l'acteur associé, par exemple dans le cas du type de relation "employé", le rôle permettra de détailler le rôle de l'employé dans la société. +Le plus souvent ce champ n'apparait pas car il n'est pas nécessaire. + +Un relation entre acteur posséde des date de validité (de .. jusqu'a ..), il est donc possible de garder un historique. +Selon le paramétrage il est aussi possible de supprimer une relation et dans ce cas l'historique ne sera pas conservé. + +  + +== Les paramètre de la Portlet + +* Titre de la portlet : Il faut indiquer un "uiLabel" (un code qui selon la langue donnera un libéllé), cela permet de personnaliser la portlet, c'est surtout utile dès que cette portlet est utilisé plusieurs fois dans la même page. Demander à un consultant technique pour trouver le uiLabel correspondant à votre besoin ou pour en définir un nouveau. +* Role type From : Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. Habituellement, ce paramètre est laissé vide. +* Groupe de role : Si le champ Role from est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle d'origine sera alors affiché dans la liste +* Type de relation (Party Relationship Type Id) : permer d'indiquer le type de relation à utiliser pour filtrer les relations à afficher, ou laisser le champ vide pour les afficher toutes et donc montrer ce champ. +* Rôle de l'acteur associé (Role type To) : Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. +* Groupe de role : Si le champ Role To est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle de destination sera alors affiché dans la liste. +* Le show edit menu Standard: s'il est positionné à N alors les bouttons "ajout", "édition" et "suppression n'apparaissent pas +* Gestion de l'historique (show history button): s'il est positionné à N alors les bouttons "invalider" et "afficher l'historique" apparaissent pas +* Bouton suppression (Show Delete button) : s'il est positionné à N alors le bouttons "suppression" n'apparait pas, souvent utilisé pour forcer uniquement l'usage de l'historisation. + + +== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours (et celles du composant) + + +== Remarques Techniques + +Cette portlet ne liste que la table PartyRelationship avec partyIdFrom == le partyId selectionné. +Une seconde portlet existe pour partyIdTo = partyId + +Quand les roles ne sont pas précisé (à la saisie ou en paramétrage), la valeur _NA_ est utilisé. \ No newline at end of file diff --git a/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRelationTos.adoc b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRelationTos.adoc new file mode 100644 index 000000000..658371980 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRelationTos.adoc @@ -0,0 +1,45 @@ + += Liste des acteurs auxquels il est associés . + +Cette portlet permet d'afficher Les acteurs auxquels est associés l'acteur sélectionné. +L'affichage et son comportement dependent de son paramétrage. + +Pour caractériser la relation entre deux acteurs, il faut préciser le "type de relation", le plus courament cette portlet est utilisé pour un type de relation précis, par exemple + +* "Contact", c'est à dire lister tous les commerciaux ou les clients pour lequel il est un contact; +* "Employé", c'est à dire lister la ou les sociétés où une personne est employé; +* "Membre d'équipe", c'est à dire lister les groupes ou les services auxquels l'acteur est inscrit; +* $$...$$ + +Pour une relation entre acteur, il est aussi possible de préciser le rôle de l'acteur associé, par exemple dans le cas du type de relation "employé", le rôle permettra de détailler le rôle de l'employé dans la société. +Le plus souvent ce champ n'apparait pas car il n'est pas nécessaire. + +Un relation entre acteur posséde des date de validité (de .. jusqu'a ..), il est donc possible de garder un historique. +Selon le paramétrage il est aussi possible de supprimer une relation et dans ce cas l'historique ne sera pas conservé. + +  + +== Les paramètre de la Portlet + +* Titre de la portlet : Il faut indiquer un "uiLabel" (un code qui selon la langue donnera un libéllé), cela permet de personnaliser la portlet, c'est surtout utile dès que cette portlet est utilisé plusieurs fois dans la même page. Demander à un consultant technique pour trouver le uiLabel correspondant à votre besoin ou pour en définir un nouveau. +* Role type From : Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. Habituellement, ce paramètre est laissé vide. +* Groupe de role : Si le champ Role from est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle d'origine sera alors affiché dans la liste +* Type de relation (Party Relationship Type Id) : permer d'indiquer le type de relation à utiliser pour filtrer les relations à afficher, ou laisser le champ vide pour les afficher toutes et donc montrer ce champ. +* Rôle de l'acteur associé (Role type To) : Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. +* Groupe de role : Si le champ Role To est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle de destination sera alors affiché dans la liste. +* Le show edit menu Standard: s'il est positionné à N alors les bouttons "ajout", "édition" et "suppression n'apparaissent pas +* Gestion de l'historique (show history button): s'il est positionné à N alors les bouttons "invalider" et "afficher l'historique" apparaissent pas +* Bouton suppression (Show Delete button) : s'il est positionné à N alors le bouttons "suppression" n'apparait pas, souvent utilisé pour forcer uniquement l'usage de l'historisation. + + +== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours (et celles du composant) + + +== Remarques Techniques + +Cette portlet ne liste que la table PartyRelationship avec partyIdTo == le partyId selectionné. +Une seconde portlet existe pour partyIdFrom = partyId + +Quand les roles ne sont pas précisé (à la saisie ou en paramétrage), la valeur _NA_ est utilisé. \ No newline at end of file diff --git a/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRoles.adoc b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRoles.adoc new file mode 100644 index 000000000..d72644826 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyRoles.adoc @@ -0,0 +1,25 @@ + += Liste des roles de l'acteur. + +Cette portlet affiche la liste des roles de l'acteur, tous ou seulement ceux faisant partie d'un groupe de rôle, celui-ci étant alors affiché dans le titre de la portlet, cela depend du paramétrage de la portlet dans cette page. + +S'il n'y a pas de groupe de rôle de paramétré alors lors de l'ajout d'un rôle, il est possible de commencer par sélectionner un rôle père puis un de ses fils. + +S'il y a un groupe de rôle de paramétré alors lors de l'ajout d'un rôle, il ne sera possible de choisir que parmi les rôles du groupe. + +  + +== Limitations ou futures fonctionnalitées + +* Actuellement c'est le Role père qui permet de regrouper plusieurs rôle, donc il n'est pas possible pour un rôle d'être dans plusieurs "groupe". + + +== Les paramètre de la Portlet + +* Le show edit Standard: s'il est positionné à N alors les bouttons "ajout" et "suppression n'apparaissent pas, la valeur par défaut est Y +* Groupe de role : indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'ajout. La liste des rôles affiché est alors filtré seul ceux du groupe sont affiché. + + +== Règles de Securité + +* PORTAL_ADMIN est nécessaire pour pouvoir éditer les paramètres de la portlet diff --git a/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyUserLogins.adoc b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyUserLogins.adoc new file mode 100644 index 000000000..32bf96b0b --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/FR/HELP_PartyUserLogins.adoc @@ -0,0 +1,29 @@ + += Portlet de Liste des utilisateurs de connexion d'un acteur. + +Cette portlet liste tous les utilisateurs de connexion associés à un acteur. +La plupart du temps, il n'y a qu'un utilisateur de connexion par acteur, mais pour les acteurs de type société, il peut être utile de pouvoir créer de multiple utilisateurs, plutot que de créer des acteurs pour chaque employé(e). + +Un utilisateur de connexion permet de se connecter à l'application OFBiz. +Les groupes de sécurité auxquels il est associé déterminent les écrans qu'il peut voir et les fonctions qu'il peux excécuter. + +  + +== Limitations ou futures fonctionnalitées + +* Seul les 5 premiers utilisateur de connexion sont affichés + + +== Les paramètres de la Portlet + +* Le show screenlet menu Standard, il permet de cacher le menu de la portlet (refresh et ajout d'un utilisateur de connexion) +* Le show edit menu Standard: si il est positionné à N alors le bouton "ajout d'un utilisateur de connexion" n'apparait pas +* security Group Id : Si un groupe de sécurité est indiqué alors le bouton "ajout d'un utilisateur de connexion" associe ce groupe de sécurité à l'utilisateur de connexion juste après sa création + + +== Règles de Securité + +* SECURITY_VIEW est nécessaire pour voir la liste des groupes de sécurité +* PARTYMGR_CREATE est nécessaire pour avoir le bouton "ajout d'un utilisateur de connexion", ainsi que SECURITY_CREATE si il y a un ajout de groupe de securité en automatique (cf paramètre Security Group Id) +* PARTYMGR_UPDATE et SECURITY_ADMIN sont nécessaires pour pourvoir éditer les informations de sécurité d'un utilisateur de connexion (le rendre valide ou non, changer son mot de passe, ...) et pour gérer les groupes de sécurité associés à un utilisateur de connexion. +* Quand un utilisateur accéde à cette portlet pour lui-même, il peut changer son mot de passe diff --git a/partyfjs/src/docs/asciidoc/_include/FR/HELP_SelectParty.adoc b/partyfjs/src/docs/asciidoc/_include/FR/HELP_SelectParty.adoc new file mode 100644 index 000000000..bd1f6cf39 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/FR/HELP_SelectParty.adoc @@ -0,0 +1,16 @@ + += Portlet Sélectionner un acteur. + +Cette portlet est utiliser pour sélectionner-choisir un acteur, pour afficher la page portail en cours. + +  + +== Les paramètre de la Portlet + +* uri pour le lookup: la valeur par défault est lookupParty, ce paramètre est utile pour les pages portail à usage d'un "groupe" d'acteur, par exemple avec un filtre sur le type d'acteur, son role, .... Demander à un consultant technique pour avoir la liste de lookup existant ou pour en définir un nouveau + + + +== Règles de Securité + +* aucune, (celles du composant) diff --git a/partyfjs/src/docs/asciidoc/_include/profile/ContactMechMgmt.adoc b/partyfjs/src/docs/asciidoc/_include/profile/ContactMechMgmt.adoc new file mode 100644 index 000000000..f5fdc15a0 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/profile/ContactMechMgmt.adoc @@ -0,0 +1,48 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// + += Contact Mechanism +This screenlet displays the Contact Mechism (adress, telephone, mail, ....) +of a business object: actor, facility, order, ... + +Depending on the Contact Mechism +(postal address, email, phone, ...), the display and entry differs. +In all cases, it is possible to clarify the purpose of the Contact Mechism +(billing address, and / or delivery, and / or ...) + +A Contact Mechism +may be associated with one or more business object, for example, postal delivery address of a client will also associated with its various orders or quotes. +It is therefore very rare to delete a Contact Mech., it is by cons, possible invalidate it, because each Contact Mech. +possesses the expiry date (from date .. thru date ..) + +By default only valid Contact Mechism +are displayed and there is a button to display the history + +== Screenlet parameters +* Setting the field Telephone: This field can be detailed by three additional subfield (pay, region, ext), each may or may not be used. +* It is possible to show or not each of the menu options to add +* It is possible, for each type of addition to give a purpose which will be used when creating + + +== Security rules +* nothing at the screenlet level, only at the update service level (and component rules) + + +== Technical remarks +This portlet displays the Contact Mechism if it receive a partyId, or a facilityId or a orderId or a workEffortId diff --git a/partyfjs/src/docs/asciidoc/_include/profile/PartyInfo.adoc b/partyfjs/src/docs/asciidoc/_include/profile/PartyInfo.adoc new file mode 100644 index 000000000..ebe4da7bc --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/profile/PartyInfo.adoc @@ -0,0 +1,30 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Party Information. +This screenlet show a information summary for a Party. +With the edit button you can change information and see the details. + +Summary and detail are adapted for Person or Party Group + +== screenlet parameters +* Standard show screenlet menu +* Standard show edit menu : if value = N "edit button" not appears + +== Security rules +* nothing at the screenlet level, only at the update service level (and component rules) diff --git a/partyfjs/src/docs/asciidoc/_include/profile/PartyRelationFroms.adoc b/partyfjs/src/docs/asciidoc/_include/profile/PartyRelationFroms.adoc new file mode 100644 index 000000000..03c26680c --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/profile/PartyRelationFroms.adoc @@ -0,0 +1,64 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Associated parties management +This screenlet displays the parties associated with the selected Party. +The display and behavior depend on its setting. + +To characterize the relationship between two parties, we must specify the "relationship type", +the more fluently this screenlet is used for a specific relationship Type, eg + +* "Contact", ie list all the contacts of a sales reps or a customer; +* "Employee", ie list all employees of a company; +* "Team Member", ie list all team members or service members; +* $$...$$ + +For a relationship between Party, it is also possible to specify the role of the party associated, for example in the case of +Relationship Type "employee", the role will detail the role of the employee in the company. + +In most cases this field does not appear because it is not necessary. + +A relationship between Party possesses validity date (from date .. thru date ..), it is possible to keep a history. +Depending on the setting it is also possible to delete a relationship in which case the history will not be retained. + +== Screenlet parameters +* Screenlet Title: You must include a "UiLabel" (a code according to a language that give wording), it allows you to customize the + screenlet Title, this is useful when the screenlet is used several times in the same page. Ask a technical consultant to find the + UILabel corresponding to your needs, or to define a new one. +* Role From Type: If this field is filled, the list displayed will be filter (only this role) and in editing (add or change) this value + will be used, the role field will not be shown to the user. Usually, this parameter is left blank. +* Role Group : If the field Role From is empty, indicates the group from which the role will be selectable in the edit screen (add - change). + The field "original role" will be displayed in the list +* Party Relationship Type Id : indicate the RelationshipType to use to filter relationships to display, or leave it blank to display all and therefore this field. +* Party associated Role (Role Type To): If this field is filled, the list displayed will be filter (only this role) and in editing + (add or change) this value will be used, field "role to" will not be displayed to the user. +* Role To Group: If the field RoleTo is empty, indicates the group from which the user will select a role in the edit screen + (add - change). The field "destination role" will be displayed in the list. +* Show edit menu Standard: if set to N then the buttons "Add", "edit" and "suppression does not appear. +* History management (show history button): if set to N then the buttons "invalidate" and "view history" not appear +* delete button (Show Delete button): if set to N then the buttons "delete" does not appear, often only used to force the use of archiving. + + +== Security rules +* nothing at the screenlet level, only at the update service level (and component rules) + + +== Technical remarks +This screenlet list only entity PartyRelationship with partyIdFrom == partyId selected. +A other screenlet exist for partyIdTo = partyId + +When Role From and/or To are not define (field by the user or in parameters), _NA_ is used. diff --git a/partyfjs/src/docs/asciidoc/_include/profile/PartyRelationTos.adoc b/partyfjs/src/docs/asciidoc/_include/profile/PartyRelationTos.adoc new file mode 100644 index 000000000..5fc07b669 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/profile/PartyRelationTos.adoc @@ -0,0 +1,63 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Party Lists to which it is associated +This screenlet displays the parties to which it is associated to the selected Party. +The display and behavior depend on its setting. + +To characterize the relationship between two parties, we must specify the "relationship type", the more fluently this screenlet is used for a specific relationship Type, eg + +* "Contact", ie list all sales reps or customer, to which he is a contact; +* "Employee", ie list all company where a person is employed; +* "Team Member", ie list all team or group to which person is member; +* $$...$$ + +For a relationship between Party, it is also possible to specify the role of the party associated, for example in the case of +Relationship Type "employee", the role will detail the role of the employee in the company. + +In most cases this field does not appear because it is not necessary. + +A relationship between Party possesses validity date (from date .. thru date ..), it is possible to keep a history. +Depending on the setting it is also possible to delete a relationship in which case the history will not be retained. + + +== Screenlet parameters +* Screenlet Title: You must include a "UiLabel" (a code according to a language that give wording), it allows you to customize + the screenlet Title this is useful when the portlet is used several times in the same page. Ask a technical consultant to find + the UILabel corresponding to your needs, or to define a new one. +* Role From Type: If this field is filled, the list displayed will be filter (only this role) and in editing (add or change) this + value will be used, the role field will not be shown to the user. Usually, this parameter is left blank. +* Role Group : If the field Role From is empty, indicates the group from which the role will be selectable in the edit screen (add - change). + The field "original role" will be displayed in the list +* Party Relationship Type Id : indicate the RelationshipType to use to filter relationships to display, or leave it blank to display all and therefore this field. +* Party associated Role (Role Type To): If this field is filled, the list displayed will be filter (only this role) and in editing (add or change) this value will be used, field "role to" will not be displayed to the user. +* Role To Group: If the field RoleTo is empty, indicates the group from which the user will select a role in the edit screen (add - change). + The field "destination role" will be displayed in the list. +* Show edit menu Standard: if set to N then the buttons "Add", "edit" and "suppression does not appear. +* History management (show history button): if set to N then the buttons "invalidate" and "view history" not appear +* delete button (Show Delete button): if set to N then the buttons "delete" does not appear, often only used to force the use of archiving. + + +== Security rules +* nothing at the screenlet level, only at the update service level (and component rules) + + +== Technical remarks +This screenlet list only entity PartyRelationship with partyIdTo == partyId selected. +A other screenlet exist for partyIdFrom = partyId + +When Role From and/or To are not define (field by the user or in parameters), _NA_ is used. diff --git a/partyfjs/src/docs/asciidoc/_include/profile/PartyRoles.adoc b/partyfjs/src/docs/asciidoc/_include/profile/PartyRoles.adoc new file mode 100644 index 000000000..e30dc0312 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/profile/PartyRoles.adoc @@ -0,0 +1,35 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Party roles list (and management) +This screenlet list the roles for the party, all or only thus which are part of a role group, the role group description is +displayed in the title of the screenlet, it depends on the setting of the screenlet on this page. + +If there is no role group set, when adding a role, you can start by selecting a parent role and after one child. + +If there is a group of role set, when adding a role, it will be possible to choose among the roles that group. + +== Limitations or futures functionalities +* Currently, role are group by using parent role, so its not possible for a role to be in multiple "group". + +== Screenlet parameters +* Show edit Standard: if set to N then the buttons "Add" and "suppression does not appear. +* Role Group : Indicates the group from which the role will be selectable in the add screen. In this case Party roles list is contain only role which are in the group + +== Security rules +* PORTAL_ADMIN is needed to edit portlet attributes diff --git a/partyfjs/src/docs/asciidoc/_include/profile/PartyUserLogins.adoc b/partyfjs/src/docs/asciidoc/_include/profile/PartyUserLogins.adoc new file mode 100644 index 000000000..54f22205c --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/profile/PartyUserLogins.adoc @@ -0,0 +1,44 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += User login list for a Party. +This screenlet list all user login associated to a Party. +Most of time a party has only one, but when party is a company, sometime it's useful to give multiple user login, +if you don't want create a party for each company's employee. + +A user login is used to connect to ofbiz application. + +Security Group give which screens he can view and which functions he can execute. + +This screenlet can be use to manage (create, modify delete) userLogin and securityGroup associated + +== Limitations or futurs fonctionnalities +* Only the first 5 user login are show + + +== Screenlet parameters +* Standard show screenlet menu +* Standard show edit menu : if value = N "add user login button" not appears +* security Group Id : if a securityGroupId is put, the 'add user login button' will automatically add this securityGroupId to the userLogin just after creating it + + +== Security rules +* SECURITY_VIEW is needed to view Security Group List +* PARTYMGR_CREATE is needed to have the "add user login button", and SECURITY_CREATE when a security group is automatically add (cf security group parameters) +* PARTYMGR_UPDATE and SECURITY_ADMIN are needed to be able to edit user login security information (enable or not user login, new password, ...) + and to manage security group list for a login +* When a user use this screenlet for him (with its own user login), he can change its password diff --git a/partyfjs/src/docs/asciidoc/_include/profile/SelectParty.adoc b/partyfjs/src/docs/asciidoc/_include/profile/SelectParty.adoc new file mode 100644 index 000000000..46eea671b --- /dev/null +++ b/partyfjs/src/docs/asciidoc/_include/profile/SelectParty.adoc @@ -0,0 +1,28 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Select a Party. +This screenlet is used to select one Party to do a current page update. + +== Screenlet parameters +* uri for lookup: default value is lookupParty, this parameters is useful when Portal Page which used this portlet is only for + a "group" of party, filter on Party Type, Role Type, .... Ask to a technical consultant to know the existing lookup or to define a new one. + + +== Security rules +* nothing (component rules only) diff --git a/partyfjs/src/docs/asciidoc/screenlet-party-profile_fr.adoc b/partyfjs/src/docs/asciidoc/screenlet-party-profile_fr.adoc new file mode 100644 index 000000000..2937f2591 --- /dev/null +++ b/partyfjs/src/docs/asciidoc/screenlet-party-profile_fr.adoc @@ -0,0 +1,409 @@ + +[[_portlet_party_pt]] += Catégorie Party_Profile (avec portlet type). + +Cette catégorie de portlet regroupe toutes les portlets de base du composant Party (acteurs) utilisant des portlets type et un fonctionnement avec des requêtes ajax (show-portlet). + +[[_portlet_selectparty]] +== Sélectionner un acteur. + +SelectParty : Sélection de l'acteur pour lequel on souhaite l'affichage de la page portail détail + +Cette portlet est utiliser pour sélectionner-choisir un acteur, pour afficher la page portail en cours. + +=== Les paramètre de la Portlet + +* uri pour le lookup: la valeur par défault est lookupParty, ce paramètre est utile pour les pages portail à usage d'un "groupe" d'acteur, par exemple avec un filtre sur le type d'acteur, son role, .... Demander à un consultant technique pour avoir la liste de lookup existant ou pour en définir un nouveau + + + +=== Règles de Securité + +* aucune, (celles du composant) + + +[[_portlet_partyinfo]] +== Descriptif d'un acteur + +PartInfo : Informations générales d'un acteur, une personne ou un groupe, affiche et édite + +Cette portlet affiche un résumé des informations de l'acteur (différent selon le type de l'acteur Personne ou Groupe d'acteur). Si une image a été associée avec cet acteur avec le type "URL vers un logo" alors elle apparait (il semble y avoir un bug, il faut avoir une connection active au composant content pour que l'image s'affiche). + +Le Bouton édition permet de voir l'ensemble des informations et au besoin de les modifier. + +=== Les paramètre de la Portlet + +* Le show screenlet menu Standard, il permet de cacher le menu de la portlet (refresh et édition) +* Le show edit menu Standard: s'il est positionné à N alors le boutton "édition" n'apparait pas + + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours de l'acteur (et celles du composant) + + +[[_portlet_partyuserlogins]] +== Liste des logins d'un acteur + +PartyUserLogins : Liste et gestion des utilisateurs de connexion d'un acteur. +Permet les modifications des informations de sécurités (mot de passe, groupe de sécurité, ...) + +Cette portlet liste tous les utilisateurs de connexion associés à un acteur. +La plupart du temps, il n'y a qu'un utilisateur de connexion par acteur, mais pour les acteurs de type société, il peut être utile de pouvoir créer de multiple utilisateurs, plutot que de créer des acteurs pour chaque employé(e). + +Un utilisateur de connexion permet de se connecter à l'application OFBiz. +Les groupes de sécurité auxquels il est associé déterminent les écrans qu'il peut voir et les fonctions qu'il peux excécuter. + +=== Limitations ou futures fonctionnalitées + +* Seul les 5 premiers utilisateur de connexion sont affichés + + +=== Les paramètres de la Portlet + +* Le show screenlet menu Standard, il permet de cacher le menu de la portlet (refresh et ajout d'un utilisateur de connexion) +* Le show edit menu Standard: si il est positionné à N alors le bouton "ajout d'un utilisateur de connexion" n'apparait pas +* security Group Id : Si un groupe de sécurité est indiqué alors le bouton "ajout d'un utilisateur de connexion" associe ce groupe de sécurité à l'utilisateur de connexion juste après sa création + + +=== Règles de Securité + +* SECURITY_VIEW est nécessaire pour voir la liste des groupes de sécurité +* PARTYMGR_CREATE est nécessaire pour avoir le bouton "ajout d'un utilisateur de connexion", ainsi que SECURITY_CREATE si il y a un ajout de groupe de securité en automatique (cf paramètre Security Group Id) +* PARTYMGR_UPDATE et SECURITY_ADMIN sont nécessaires pour pourvoir éditer les informations de sécurité d'un utilisateur de connexion (le rendre valide ou non, changer son mot de passe, ...) et pour gérer les groupes de sécurité associés à un utilisateur de connexion. +* Quand un utilisateur accéde à cette portlet pour lui-même, il peut changer son mot de passe + + +[[_portlet_partyroles]] +== Liste des rôles de l'acteur + +PartyRoles : Liste tous (ou uniquement ceux d'un groupe de rôle) les rôle de l'acteur. +Affichage et édition + +Cette portlet affiche la liste des roles de l'acteur, tous ou seulement ceux faisant partie d'un groupe de rôle, celui-ci étant alors affiché dans le titre de la portlet, cela depend du paramétrage de la portlet dans cette page. + +S'il n'y a pas de groupe de rôle de paramétré alors lors de l'ajout d'un rôle, il est possible de commencer par sélectionner un rôle père puis un de ses fils. + +S'il y a un groupe de rôle de paramétré alors lors de l'ajout d'un rôle, il ne sera possible de choisir que parmi les rôles du groupe. + +=== Limitations ou futures fonctionnalitées + +* Actuellement c'est le Role père qui permet de regrouper plusieurs rôle, donc il n'est pas possible pour un rôle d'être dans plusieurs "groupe". + + +=== Les paramètre de la Portlet + +* Le show edit Standard: s'il est positionné à N alors les bouttons "ajout" et "suppression n'apparaissent pas, la valeur par défaut est Y +* Groupe de role : indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'ajout. La liste des rôles affiché est alors filtré seul ceux du groupe sont affiché. + + +=== Règles de Securité + +* PORTAL_ADMIN est nécessaire pour pouvoir éditer les paramètres de la portlet + + +[[_portlet_contactmechmgmt]] +== Coordonnées de l'acteur + +ContactMechMgmt : Liste les coordonnées de l'acteur, affichage et édition + +Cette portlet permet d'afficher les coordonnées d'un objet metier, acteur, magasin, commande, ... + +Selon le type de coordonnée (adresse postal, mail, téléphone, ...), l'affichage et la saisie diffère. +Dans tous les cas, il est possible de préciser le but de la coordonnée (adresse de facturation, et/ou de livraison, et/ou ...) + +Une coordonnée peut être associé à un ou plusieurs objet métier, par exemple l'adresse postal de livraison d'un client sera aussi associé à ses différentes commandes ou devis. +Il est donc trés rare de supprimer une coordonnée, il est par contre, possible de l'invalider, en effet chaque coordonnée posséde des date de validité (de .. jusqu'a ..) + +Par défaut seul les coordonnées valide sont affiché et un boutton permet d'afficher l'historique + +=== Les paramètre de la Portlet + +* Paramétrage du champ N° de téléphone : ce champ peut être compéter par 3 sous-champ complémentaire (pay, région, ext), chacun peut ou non etre utilisé. +* Il est possible de faire apparaitre ou chacune des options du menu d'ajout +* Il est possible, pour chacun des types d'ajout de donner un but qui sera utilisé lors de la création + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours (et celles du composant) + + +=== Remarques Techniques + +Cette portlet fonctionne si elle reçoit un partyId, ou un facilityId ou un orderId ou un workEffortId + +[[_portlet_partyrelationfroms]] +== Liste des acteurs associés à l'acteur + +PartyRelationFroms : Liste des acteurs associés à l'acteur, le type de relation et les roles associés sont configurable. +Affichage et édition + +Cette portlet permet d'afficher les acteurs associés à l'acteur sélectionné. +L'affichage et son comportement dependent de son paramétrage. + +Pour caractériser la relation entre deux acteurs, il faut préciser le "type de relation", le plus courament cette portlet est utilisé pour un type de relation précis, par exemple + +* "Contact", c'est à dire lister tous les contacts d'un commerciale ou chez un client; +* "Employé", c'est à dire lister tous les employées d'une société; +* "Membre d'équipe", c'est à dire lister tous les membres d'une équipe ou d'un service; +* $$...$$ + +Pour une relation entre acteur, il est aussi possible de préciser le rôle de l'acteur associé, par exemple dans le cas du type de relation "employé", le rôle permettra de détailler le rôle de l'employé dans la société. +Le plus souvent ce champ n'apparait pas car il n'est pas nécessaire. + +Un relation entre acteur posséde des date de validité (de .. jusqu'a ..), il est donc possible de garder un historique. +Selon le paramétrage il est aussi possible de supprimer une relation et dans ce cas l'historique ne sera pas conservé. + +=== Les paramètre de la Portlet + +* Titre de la portlet : Il faut indiquer un "uiLabel" (un code qui selon la langue donnera un libéllé), cela permet de personnaliser la portlet, c'est surtout utile dès que cette portlet est utilisé plusieurs fois dans la même page. Demander à un consultant technique pour trouver le uiLabel correspondant à votre besoin ou pour en définir un nouveau. +* Role type From : Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. Habituellement, ce paramètre est laissé vide. +* Groupe de role : Si le champ Role from est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle d'origine sera alors affiché dans la liste +* Type de relation (Party Relationship Type Id) : permer d'indiquer le type de relation à utiliser pour filtrer les relations à afficher, ou laisser le champ vide pour les afficher toutes et donc montrer ce champ. +* Rôle de l'acteur associé (Role type To) : Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. +* Groupe de role : Si le champ Role To est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle de destination sera alors affiché dans la liste. +* Le show edit menu Standard: s'il est positionné à N alors les bouttons "ajout", "édition" et "suppression n'apparaissent pas +* Gestion de l'historique (show history button): s'il est positionné à N alors les bouttons "invalider" et "afficher l'historique" apparaissent pas +* Bouton suppression (Show Delete button) : s'il est positionné à N alors le bouttons "suppression" n'apparait pas, souvent utilisé pour forcer uniquement l'usage de l'historisation. + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours (et celles du composant) + + +=== Remarques Techniques + +Cette portlet ne liste que la table PartyRelationship avec partyIdFrom == le partyId selectionné. +Une seconde portlet existe pour partyIdTo = partyId + +Quand les roles ne sont pas précisé (à la saisie ou en paramétrage), la valeur _NA_ est utilisé. + +[[_portlet_partyrelationtos]] +== Liste des acteurs auxquels il est associés + +PartyRelationTos : Liste des acteurs auxquels il est associés, le type de relation et les roles associés sont configurable. +Affichage et édition + +Cette portlet permet d'afficher Les acteurs auxquels est associés l'acteur sélectionné. +L'affichage et son comportement dependent de son paramétrage. + +Pour caractériser la relation entre deux acteurs, il faut préciser le "type de relation", le plus courament cette portlet est utilisé pour un type de relation précis, par exemple + +* "Contact", c'est à dire lister tous les commerciaux ou les clients pour lequel il est un contact; +* "Employé", c'est à dire lister la ou les sociétés où une personne est employé; +* "Membre d'équipe", c'est à dire lister les groupes ou les services auxquels l'acteur est inscrit; +* $$...$$ + +Pour une relation entre acteur, il est aussi possible de préciser le rôle de l'acteur associé, par exemple dans le cas du type de relation "employé", le rôle permettra de détailler le rôle de l'employé dans la société. +Le plus souvent ce champ n'apparait pas car il n'est pas nécessaire. + +Un relation entre acteur posséde des date de validité (de .. jusqu'a ..), il est donc possible de garder un historique. +Selon le paramétrage il est aussi possible de supprimer une relation et dans ce cas l'historique ne sera pas conservé. + +=== Les paramètre de la Portlet + +* Titre de la portlet : Il faut indiquer un "uiLabel" (un code qui selon la langue donnera un libéllé), cela permet de personnaliser la portlet, c'est surtout utile dès que cette portlet est utilisé plusieurs fois dans la même page. Demander à un consultant technique pour trouver le uiLabel correspondant à votre besoin ou pour en définir un nouveau. +* Role type From : Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. Habituellement, ce paramètre est laissé vide. +* Groupe de role : Si le champ Role from est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle d'origine sera alors affiché dans la liste +* Type de relation (Party Relationship Type Id) : permer d'indiquer le type de relation à utiliser pour filtrer les relations à afficher, ou laisser le champ vide pour les afficher toutes et donc montrer ce champ. +* Rôle de l'acteur associé (Role type To) : Si ce champ est renseigné, cela permet de filtrer la liste affiché et dans l'écran d'édition (ajout ou modification) ce sera la valeur utilisé, le champ role ne sera pas affiché à l'utilisateur. +* Groupe de role : Si le champ Role To est vide, indique le groupe de rôle parmi lequel l'utisateur choisira dans l'écran d'édition (ajout - modification). Le champ rôle de destination sera alors affiché dans la liste. +* Le show edit menu Standard: s'il est positionné à N alors les bouttons "ajout", "édition" et "suppression n'apparaissent pas +* Gestion de l'historique (show history button): s'il est positionné à N alors les bouttons "invalider" et "afficher l'historique" apparaissent pas +* Bouton suppression (Show Delete button) : s'il est positionné à N alors le bouttons "suppression" n'apparait pas, souvent utilisé pour forcer uniquement l'usage de l'historisation. + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours (et celles du composant) + + +=== Remarques Techniques + +Cette portlet ne liste que la table PartyRelationship avec partyIdTo == le partyId selectionné. +Une seconde portlet existe pour partyIdFrom = partyId + +Quand les roles ne sont pas précisé (à la saisie ou en paramétrage), la valeur _NA_ est utilisé. + +[[_portlet_partyattributes]] +== Les attribues d'un acteur + +PartyAttributes : Liste des attribues d'un acteur, liste et gestion + +Cette portlet permet d'afficher la liste des attribues d'un acteur. +Les attribues permettent ajouter des informations sans modifier la structure de la base de données. +Il est possible d'assister ou de contraindre l'ajout d'attribues en fonction du type de l'acteur ou de ces roles. + +=== Les paramètre de la Portlet + +* aucun + + +=== Limitations ou futures fonctionnalitées + +* Actuellement, la saisi est libre, il n'y a pas de possibilité d'ajouter des contraintes ou des propositions attribues lors de l'ajout. Par la suite il sera possible de paramétrer un Type d'acteur ou un rôle ou un groupe de rôle, ce qui permettra d'avoir à saisie uniquement les attribues paramétré pour ce type d'acteur ou ce rôle ou pour les rôles de ce groupe. +* Dans le futur il y aura une portlet generique de gestion d'attribue pour un objet métier. + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle au niveau du service de mise à jours (partyBasePermissionCheck) (et celles du composant) + + +[[_portlet_partyavssettings]] +== Réglage Avs d'un acteur + +PartyAvsSettings : Réglage ou validation Avs d'un acteur (criblage de fraude) + +Pour modifier la valeur, il suffit de clicker directement sur le champ + +Cette portlet permet d'afficher si l'acteur à un indicateur Avs. + +=== Les paramètre de la Portlet + +* aucun + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle du composant + + +[[_portlet_partyloyaltypoints]] +== Points de fidélité d'un acteur + +PartyLoyaltyPoints : Affichage des points de fidélité d'un acteur + +Cette portlet permet d'afficher les points de fidélité cumulé lors de la saisie des commandes. + +=== Les paramètre de la Portlet + +* aucun + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle du composant + + +[[_portlet_partyidentlists]] +== N° d'identifications d'un acteur + +PartyIdentLists : Liste et gestion des n° d'identifications d'un acteur + +Cette portlet liste et gère les n° d'identification d'un acteur, un n° d'identification correspond à un type de n° (n°sécurité social, n° de siret, n° de tva, ...) et un champ libre. + +=== Les paramètre de la Portlet + +* Type de n° d'identification père : s'il est vide il est possible de saisir tous les types de n° d'identification, dans le cas contraire seul les fils de type de n° d'identification seront accessible. + + +=== Limitations ou futures fonctionnalitées + +* Il est possible de saisir qu'une seul valeur par type de n° d'identification . +* Il n'existe pas de portlet de gestion des types de n° d'identification. + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement uniquement celle au niveau du service de mise à jours (partyBasePermissionCheck) + + +=== Remarques Techniques + +portletLongId="PartyIdentifications" + +[[_portlet_partynotes]] +== Les notes d'un acteur + +PartyNotes : Liste et gestion des notes d'un acteur + +Cette portlet liste et gère -ajout modification et association- les notes associées à un acteur. +Il est possible d'associer une note existante, c'est pour cela que le n° d'identifiant de la note apparait dans la liste. + +=== Les paramètre de la Portlet + +* Aucun + + +=== Limitations ou futures fonctionnalitées + +* Il y aura des paramètres pour supprimer les boutons d'association, d'ajout et de modification. + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle du composant + + +[[_portlet_partypaymentmethods]] +== Les méthodes de payment d'un acteur + +PartyPaymentMethods : Liste et gestion des méthodes de payment d'un acteur + +Cette portlet liste et gère -ajout modification et invalidation- les moyens de paiement associées à un acteur. +Pour chaque mode de paiement ajouté il y a interval de date de validité, la date de début est positionné à la date du jour de la création et la date de fin est vide. +Pour un mode de paiement il n'est pas possible de le supprimer mais seulement de le rendre invalide (l'icone horloge à droite). Dans le menu de la portlet, la première option permet d'afficher les mode de paiement avec une date de fin de validité inférieur à la date du jour. + +=== Les paramètre de la Portlet + +* Aucun + + +=== Limitations ou futures fonctionnalitées + +* Il y aura des paramètres pour rendre chaque bouton du menu optionnel. +* 4 moyens de paiement sont géré Carte cadeau, Compte de virement, Carte Banquaire, Compte de facturation si vous souhaitez en ajouter un supplémentaire en utilisant le service générique creatPaymentMethod, celui-ci n'existe pas. + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle du composant + + +[[_portlet_partycontents]] +== Elément de contenu d'un acteur + +PartyContents : Liste et gestion des éléments de contenu associé à un acteur + +Cette portlet liste et gère -ajout modification et suppression- les fichiers joints à un acteur. + +Si on associe une image avec le type "URL vers un logo", alors elle apparaitra dans la portlet <<_portlet_partyinfo,PartyInfo>> + +=== Les paramètre de la Portlet + +* Aucun + + +=== Limitations ou futures fonctionnalitées + +* Il y aura des paramètres pour rendre chaque bouton du menu optionnel. +* Il y aura des paramètres pour faire apparaitre ou non les différents champs complémentaires Type de contenu et type Mime. +* Il n'existe pas de portlet pour gérer les différents type de contenu. + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement uniquement celle au niveau du service de mise à jours CONTENTMGR_CREATE, CONTENTMGR_UPDATE and service ensurePartyRole (_NA_ ou OWNER). + + +[[_portlet_partylastvisits]] +== Dernière "visite" d'un acteur + +PartyLastVisits : Les 5 dernières visites (sur le site web de l'instance OFBiz) d'un acteur + +Cette portlet liste les 5 dernières visites (sur le site web de l'instance OFBiz) d'un acteur. + +TODO à détailler + +=== Les paramètre de la Portlet + +* Aucun + + +=== Règles de Securité + +* aucune au niveau de la portlet, uniquement celle du composant diff --git a/partyfjs/src/docs/asciidoc/screenlet-party.adoc b/partyfjs/src/docs/asciidoc/screenlet-party.adoc new file mode 100644 index 000000000..b0de4f79e --- /dev/null +++ b/partyfjs/src/docs/asciidoc/screenlet-party.adoc @@ -0,0 +1,35 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Party +== Category Party_Profile. +This category is for Party component with a focus on all detail for a Party. + +include::_include/profile/PartyInfo.adoc[leveloffset=+2] + +include::_include/profile/ContactMechMgmt.adoc[leveloffset=+2] + +include::_include/profile/PartyRoles.adoc[leveloffset=+2] + +include::_include/profile/PartyUserLogins.adoc[leveloffset=+2] + +include::_include/profile/PartyRelationFroms.adoc[leveloffset=+2] + +include::_include/profile/PartyRelationTos.adoc[leveloffset=+2] + +include::_include/profile/SelectParty.adoc[leveloffset=+2] \ No newline at end of file diff --git a/partyfjs/template/editbillingaccountterm.ftl b/partyfjs/template/editbillingaccountterm.ftl new file mode 100755 index 000000000..119e8d747 --- /dev/null +++ b/partyfjs/template/editbillingaccountterm.ftl @@ -0,0 +1,122 @@ +<#-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<#assign termTypes = delegator.findByAnd("TermType",{'parentTypeId':'FINANCIAL_TERM'})> +<#assign uomIds = delegator.findByAnd("Uom",{'uomTypeId':'TIME_FREQ_MEASURE'})> +
      + + + + +
      ${uiLabelMap.PartyTerms} + + <#assign billingAccountTerms = delegator.findByAnd("BillingAccountTerm",{'billingAccountId',parameters.billingAccountId})> + <#if billingAccountTerms?has_content> + <#list billingAccountTerms as billingAccountTerm> + <#assign termType = billingAccountTerm.getRelatedOneCache("TermType")> + + <#if parameters.billingAccountTermId?has_content && parameters.billingAccountTermId = billingAccountTerm.billingAccountTermId> + + <#else> + + + + + + + + + +
      + <#-- Edit existing term type --> +
      + + + <#if termTypes?has_content> + ${uiLabelMap.PartyTermType} + + + ${uiLabelMap.PartyTermValue} + + <#if uomIds?has_content> + ${uiLabelMap.CommonUom} + + + ${uiLabelMap.CommonUpdate} + ${uiLabelMap.CommonCancel} +
      +
      + <#-- Display existing term type --> + <#if termType?has_content>${termType.get("description",locale)}<#else>${billingAccountTerm.termTypeId} + (${uiLabelMap.PartyTermValue}:${billingAccountTerm.termValue?if_exists}) + <#if billingAccountTerm.uomId?has_content> + <#assign uom = billingAccountTerm.getRelatedOneCache("Uom")> + (${uiLabelMap.CommonUom}:${uom.description?if_exists}) + + + + +
      + + +
      +
      +
      + + + + + +
      + + <#if termTypes?has_content> + ${uiLabelMap.PartyTermType} + + + ${uiLabelMap.PartyTermValue} + + <#if uomIds?has_content> + ${uiLabelMap.CommonUom} + + +

      +
      +
      diff --git a/partyfjs/webapp/partymgrfjs/WEB-INF/controller.xml b/partyfjs/webapp/partymgrfjs/WEB-INF/controller.xml new file mode 100644 index 000000000..ad399cbd0 --- /dev/null +++ b/partyfjs/webapp/partymgrfjs/WEB-INF/controller.xml @@ -0,0 +1,496 @@ + + + + + + + + PartyFjs Component Site Configuration File + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/partyfjs/webapp/partymgrfjs/WEB-INF/web.xml b/partyfjs/webapp/partymgrfjs/WEB-INF/web.xml new file mode 100644 index 000000000..b8266a979 --- /dev/null +++ b/partyfjs/webapp/partymgrfjs/WEB-INF/web.xml @@ -0,0 +1,93 @@ + + + + + + Apache OFBiz - PartyMgr Application FJS + FJS for PartyMgr Application of the Apache OFBiz Project + + + A unique name used to identify/recognize the local dispatcher for the Service Engine + localDispatcherNamepartymgrfjs + + + The Name of the Entity Delegator to use, defined in entityengine.xml + entityDelegatorNamedefault + + + The location of the main-decorator screen to use for this webapp; referred to as a context variable in screen def XML files. + mainDecoratorLocation + + component://partyfjs/widget/CommonScreens.xml + + + Remove unnecessary whitespace from HTML output. + compressHTML + false + + + + ControlFilter + ControlFilter + org.apache.ofbiz.webapp.control.ControlFilter + + allowedPaths + /error:/control:/select:/index.html:/index.jsp:/default.html:/default.jsp:/images:/js:/ws + + + redirectPath + /control/main + + + + ContextFilter + ContextFilter + org.apache.ofbiz.webapp.control.ContextFilter + + + ControlFilter + /* + + + ContextFilter + /* + + + org.apache.ofbiz.webapp.control.ControlEventListener + org.apache.ofbiz.webapp.control.LoginEventListener + + + + + Main Control Servlet + ControlServlet + ControlServlet + org.apache.ofbiz.webapp.control.ControlServlet + 1 + + ControlServlet/control/* + + + + index.jsp + index.html + index.htm + + diff --git a/partyfjs/webapp/partymgrfjs/error/error.jsp b/partyfjs/webapp/partymgrfjs/error/error.jsp new file mode 100644 index 000000000..50a18662e --- /dev/null +++ b/partyfjs/webapp/partymgrfjs/error/error.jsp @@ -0,0 +1,34 @@ +<%@ page import="org.apache.ofbiz.base.util.*" %> + + +Open For Business Message + + + +<% String errorMsg = (String) request.getAttribute("_ERROR_MESSAGE_"); %> + + +
      +
      + + + + +
      + + + + + + + +
      +
      :ERROR MESSAGE:
      +
      +
      <%=UtilFormatOut.replaceString(errorMsg, "\n", "
      ")%>
      +
      +
      +
      +
      + + \ No newline at end of file diff --git a/partyfjs/webapp/partymgrfjs/index.jsp b/partyfjs/webapp/partymgrfjs/index.jsp new file mode 100644 index 000000000..598a0734f --- /dev/null +++ b/partyfjs/webapp/partymgrfjs/index.jsp @@ -0,0 +1 @@ +<%response.sendRedirect("control/main");%> \ No newline at end of file diff --git a/partyfjs/widget/CommonScreens.xml b/partyfjs/widget/CommonScreens.xml new file mode 100644 index 000000000..8c7267c42 --- /dev/null +++ b/partyfjs/widget/CommonScreens.xml @@ -0,0 +1,123 @@ + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + + +
      + +
      +
      +
      + + +
      + + +
      + + + + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      + + + +
      + + + + +
      +
      + + +
      + + + + + + + +
      +
      +
      diff --git a/partyfjs/widget/PartyFjsForms.xml b/partyfjs/widget/PartyFjsForms.xml new file mode 100644 index 000000000..6ffff141d --- /dev/null +++ b/partyfjs/widget/PartyFjsForms.xml @@ -0,0 +1,468 @@ + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + + + +
      + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + + +
      + + + + + + + + + + + + +
      + +
      + + + + + + + + + + + + + +
      + + + + + - + <#if layoutSettings.styleSheets?has_content> <#--layoutSettings.styleSheets is a list of style sheets. So, you can have a user-specified "main" style sheet, AND a component style sheet.--> diff --git a/themefrontjs/widget/Theme.xml b/themefrontjs/widget/Theme.xml index 77d4e4ade..ff177cfb3 100644 --- a/themefrontjs/widget/Theme.xml +++ b/themefrontjs/widget/Theme.xml @@ -32,17 +32,14 @@ under the License. - - + - - - - - + + + - \ No newline at end of file + From 7beb2bf36b4b505c77ab77648c167c9ee329e839 Mon Sep 17 00:00:00 2001 From: holivier Date: Tue, 9 Jun 2020 11:06:37 +0200 Subject: [PATCH 09/24] vuejs: correction for theme old name --- vuejs/widget/CommonScreens.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuejs/widget/CommonScreens.xml b/vuejs/widget/CommonScreens.xml index 35fc0b17c..68a811bb3 100644 --- a/vuejs/widget/CommonScreens.xml +++ b/vuejs/widget/CommonScreens.xml @@ -206,7 +206,7 @@ under the License. - +
      From 1dfed7154cf751b5e51cc5fce36b1d200f77f529 Mon Sep 17 00:00:00 2001 From: holivier Date: Tue, 9 Jun 2020 11:10:45 +0200 Subject: [PATCH 10/24] first corrections after sonarcloud remarks --- examplefjs/ofbiz-component.xml | 6 ---- examplefjs/servicedef/secas.xml | 2 +- examplefjs/webapp/examplefjs/WEB-INF/web.xml | 1 - examplefjs/webapp/examplefjs/error/error.jsp | 34 -------------------- vuejs/webapp/vuejs/WEB-INF/web.xml | 4 +++ 5 files changed, 5 insertions(+), 42 deletions(-) delete mode 100644 examplefjs/webapp/examplefjs/error/error.jsp diff --git a/examplefjs/ofbiz-component.xml b/examplefjs/ofbiz-component.xml index 4c77422c4..0888b19c5 100644 --- a/examplefjs/ofbiz-component.xml +++ b/examplefjs/ofbiz-component.xml @@ -29,9 +29,6 @@ under the License. - - - @@ -39,9 +36,6 @@ under the License. - diff --git a/examplefjs/servicedef/secas.xml b/examplefjs/servicedef/secas.xml index be070a63c..4a3984b35 100644 --- a/examplefjs/servicedef/secas.xml +++ b/examplefjs/servicedef/secas.xml @@ -20,7 +20,7 @@ under the License. - diff --git a/examplefjs/webapp/examplefjs/WEB-INF/web.xml b/examplefjs/webapp/examplefjs/WEB-INF/web.xml index 2a252e08b..cdff0ee1d 100644 --- a/examplefjs/webapp/examplefjs/WEB-INF/web.xml +++ b/examplefjs/webapp/examplefjs/WEB-INF/web.xml @@ -72,7 +72,6 @@ under the License. org.apache.ofbiz.webapp.control.ControlEventListener org.apache.ofbiz.webapp.control.LoginEventListener - Main Control Servlet diff --git a/examplefjs/webapp/examplefjs/error/error.jsp b/examplefjs/webapp/examplefjs/error/error.jsp deleted file mode 100644 index 50a18662e..000000000 --- a/examplefjs/webapp/examplefjs/error/error.jsp +++ /dev/null @@ -1,34 +0,0 @@ -<%@ page import="org.apache.ofbiz.base.util.*" %> - - -Open For Business Message - - - -<% String errorMsg = (String) request.getAttribute("_ERROR_MESSAGE_"); %> - - -
      -
      - - - - -
      - - - - - - - -
      -
      :ERROR MESSAGE:
      -
      -
      <%=UtilFormatOut.replaceString(errorMsg, "\n", "
      ")%>
      -
      -
      -
      -
      - - \ No newline at end of file diff --git a/vuejs/webapp/vuejs/WEB-INF/web.xml b/vuejs/webapp/vuejs/WEB-INF/web.xml index 0c7f2d1f4..f0a26c185 100644 --- a/vuejs/webapp/vuejs/WEB-INF/web.xml +++ b/vuejs/webapp/vuejs/WEB-INF/web.xml @@ -30,6 +30,10 @@ under the License. /dist + + ControlFilter + /* + dist/index.html From a0dbbf2fa185cb0fc68ec78313d89f372e067218 Mon Sep 17 00:00:00 2001 From: holivier Date: Tue, 9 Jun 2020 11:13:27 +0200 Subject: [PATCH 11/24] vuejs: patch OFBIZ-11768 synchro after ofbiz commit f21dbd6 for add vuejs as children of html --- ...ejs-as-children-tag-of-html-tag-in-screen.patch | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vuejs/ofbizCommit2add/OFBIZ-11768_0001-add-vuejs-as-children-tag-of-html-tag-in-screen.patch b/vuejs/ofbizCommit2add/OFBIZ-11768_0001-add-vuejs-as-children-tag-of-html-tag-in-screen.patch index 4f747bec7..41f152f5f 100644 --- a/vuejs/ofbizCommit2add/OFBIZ-11768_0001-add-vuejs-as-children-tag-of-html-tag-in-screen.patch +++ b/vuejs/ofbizCommit2add/OFBIZ-11768_0001-add-vuejs-as-children-tag-of-html-tag-in-screen.patch @@ -1,4 +1,4 @@ -From 55fc09561ec975be0fc91f4eaa4f3c64175f330b Mon Sep 17 00:00:00 2001 +From 912a0cc31a1bf75625537bc657946550c5b738c2 Mon Sep 17 00:00:00 2001 From: holivier Date: Wed, 27 May 2020 11:43:08 +0200 Subject: [PATCH 11/14] Improved: add vuejs as children tag of html tag in @@ -63,18 +63,18 @@ index 0ea33934fd..44657bf1a1 100644 } diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java -index b7a9cc94bc..cbca214aa1 100644 +index e03fe38027..5a4ff24fbb 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java -@@ -39,6 +39,7 @@ import org.apache.ofbiz.base.util.cache.UtilCache; - import org.apache.ofbiz.base.util.collections.MapStack; +@@ -40,6 +40,7 @@ import org.apache.ofbiz.base.util.collections.MapStack; import org.apache.ofbiz.base.util.string.FlexibleStringExpander; import org.apache.ofbiz.base.util.template.FreeMarkerWorker; + import org.apache.ofbiz.security.CsrfUtil; +import org.apache.ofbiz.widget.WidgetFactory; import org.apache.ofbiz.widget.renderer.ScreenRenderer; import org.apache.ofbiz.widget.renderer.ScreenStringRenderer; import org.apache.ofbiz.widget.renderer.html.HtmlWidgetRenderer; -@@ -126,6 +127,8 @@ public class HtmlWidget extends ModelScreenWidget { +@@ -130,6 +131,8 @@ public class HtmlWidget extends ModelScreenWidget { subWidgets.add(new HtmlTemplate(modelScreen, childElement)); } else if ("html-template-decorator".equals(childElement.getNodeName())) { subWidgets.add(new HtmlTemplateDecorator(modelScreen, childElement)); @@ -84,7 +84,7 @@ index b7a9cc94bc..cbca214aa1 100644 throw new IllegalArgumentException("Tag not supported under the platform-specific -> html tag with name: " + childElement.getNodeName()); diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java -index 97ec2258c0..453519343c 100644 +index 8a7e09d5a6..04ffde9ea8 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java @@ -1069,6 +1069,71 @@ public abstract class ModelScreenWidget extends ModelWidget { @@ -214,7 +214,7 @@ index 13263adf7d..7ea4dfa1eb 100644 public void renderImage(Appendable writer, Map context, ModelScreenWidget.ScreenImage image) throws IOException; diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java -npmindex a8cf74e68b..bb627fc23d 100644 +index a8cf74e68b..bb627fc23d 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java @@ -223,6 +223,10 @@ public class MacroScreenRenderer implements ScreenStringRenderer { From 6ad10a7a79392d6c3910b3faa323769fddfe9cd0 Mon Sep 17 00:00:00 2001 From: holivier Date: Tue, 9 Jun 2020 11:58:11 +0200 Subject: [PATCH 12/24] sonarcloud remarks corrections, remove unused files, and change table to div for Portal.vue --- partyfjs/webapp/partymgrfjs/error/error.jsp | 34 --- .../webapp/thfrontjs/js/application.js | 201 ------------------ vuejs/webapp/vuejs/src/components/Portal.vue | 6 +- 3 files changed, 2 insertions(+), 239 deletions(-) delete mode 100644 partyfjs/webapp/partymgrfjs/error/error.jsp delete mode 100644 themefrontjs/webapp/thfrontjs/js/application.js diff --git a/partyfjs/webapp/partymgrfjs/error/error.jsp b/partyfjs/webapp/partymgrfjs/error/error.jsp deleted file mode 100644 index 50a18662e..000000000 --- a/partyfjs/webapp/partymgrfjs/error/error.jsp +++ /dev/null @@ -1,34 +0,0 @@ -<%@ page import="org.apache.ofbiz.base.util.*" %> - - -Open For Business Message - - - -<% String errorMsg = (String) request.getAttribute("_ERROR_MESSAGE_"); %> - - -
      -
      - - - - -
      - - - - - - - -
      -
      :ERROR MESSAGE:
      -
      -
      <%=UtilFormatOut.replaceString(errorMsg, "\n", "
      ")%>
      -
      -
      -
      -
      - - \ No newline at end of file diff --git a/themefrontjs/webapp/thfrontjs/js/application.js b/themefrontjs/webapp/thfrontjs/js/application.js deleted file mode 100644 index 9a30e73b7..000000000 --- a/themefrontjs/webapp/thfrontjs/js/application.js +++ /dev/null @@ -1,201 +0,0 @@ -/*********************************************** -APACHE OFBiz -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -***********************************************/ - -/********************* -JQuery Columns -*********************/ -var j = 1; - -(function(jQuery) { - jQuery.fn.columns = function(options) { - - var defaults = { - colNumber: 2, - direction: 'vertical' - }; - - this.each(function() { - - var obj = jQuery(this); - var settings = jQuery.extend(defaults, options); - var totalListElements = jQuery(this).children('li').size(); - var baseColItems = Math.ceil(totalListElements / settings.colNumber); - var listClass = jQuery(this).attr('class'); - - for (i=1;i<=settings.colNumber;i++) { - if(i==1){ - jQuery(this).addClass('listCol1').wrap('
      '); - } - else if(jQuery(this).is('ul')) { - jQuery(this).parents('.listContainer'+j).append('
        '); - } - else { - jQuery(this).parents('.listContainer'+j).append('
          '); - } - jQuery('.listContainer'+j+' > ul,.listContainer'+j+' > ol').addClass(listClass); - } - - var listItem = 0; - var k = 1; - var l = 0; - - if(settings.direction == 'vertical') { - jQuery(this).children('li').each(function() { - listItem = listItem+1; - if (listItem > baseColItems*(settings.colNumber-1) ) { - jQuery(this).parents('.listContainer'+j).find('.listCol'+settings.colNumber).append(this); - } - else { - if(listItem<=(baseColItems*k)) { - jQuery(this).parents('.listContainer'+j).find('.listCol'+k).append(this); - } - else { - jQuery(this).parents('.listContainer'+j).find('.listCol'+(k+1)).append(this); - k = k+1; - } - } - }); - - jQuery('.listContainer'+j).find('ol,ul').each(function(){ - if(jQuery(this).children().size() == 0) { - jQuery(this).remove(); - } - }); - - } - - else { - jQuery(this).children('li').each(function(){ - l = l+1; - if(l <= settings.colNumber) { - jQuery(this).parents('.listContainer'+j).find('.listCol'+l).append(this); - } - else { - l = 1; - jQuery(this).parents('.listContainer'+j).find('.listCol'+l).append(this); - } - }); - } - - jQuery('.listContainer'+j).find('ol:last,ul:last').addClass('last'); - j = j+1; - - }); - }; -})(jQuery); - -/********************* -JQuery Formalize -*********************/ -var FORMALIZE = (function($, window, document, undefined) { - var PLACEHOLDER_SUPPORTED = 'placeholder' in document.createElement('input'); - var AUTOFOCUS_SUPPORTED = 'autofocus' in document.createElement('input'); - var WEBKIT = 'webkitAppearance' in document.createElement('select').style; - var IE6 = !!($.browser.msie && parseInt($.browser.version, 10) === 6); - var IE7 = !!($.browser.msie && parseInt($.browser.version, 10) === 7); - return { - go: function() { - for (var i in FORMALIZE.init) { - FORMALIZE.init[i](); - } - }, - init: { - detect_webkit: function() { - if (!WEBKIT) { - return; - } - $('html').addClass('is_webkit'); - }, - full_input_size: function() { - if (!IE7 || !$('textarea, input.input_full').length) { - return; - } - $('textarea, input.input_full').wrap(''); - }, - ie6_skin_inputs: function() { - if (!IE6 || !$('input, select, textarea').length) { - return; - } - var button_regex = /button|submit|reset/; - var type_regex = /date|datetime|datetime-local|email|month|number|password|range|search|tel|text|time|url|week/; - $('input').each(function() { - var el = $(this); - if (this.getAttribute('type').match(button_regex)) { - el.addClass('ie6_button'); - if (this.disabled) { - el.addClass('ie6_button_disabled'); - } - } - else if (this.getAttribute('type').match(type_regex)) { - el.addClass('ie6_input'); - if (this.disabled) { - el.addClass('ie6_input_disabled'); - } - } - }); - $('textarea, select').each(function() { - if (this.disabled) { - $(this).addClass('ie6_input_disabled'); - } - }); - }, - placeholder: function() { - if (PLACEHOLDER_SUPPORTED || !$(':input[placeholder]').length) { - return; - } - $(':input[placeholder]').each(function() { - var el = $(this); - var text = el.attr('placeholder'); - function add_placeholder() { - if (!el.val() || el.val() === text) { - el.val(text).addClass('placeholder_text'); - } - } - add_placeholder(); - el.focus(function() { - if (el.val() === text) { - el.val('').removeClass('placeholder_text');; - } - }).blur(function() { - add_placeholder(); - }); - el.closest('form').submit(function() { - if (el.val() === text) { - el.val(''); - } - }).on('reset', function() { - setTimeout(add_placeholder, 50); - }); - }); - }, - autofocus: function() { - if (AUTOFOCUS_SUPPORTED || !$(':input[autofocus]').length) { - return; - } - $(':input[autofocus]:visible:first').select(); - } - } - }; -})(jQuery, this, this.document); - -jQuery(document).ready(function() { - FORMALIZE.go(); -}); - diff --git a/vuejs/webapp/vuejs/src/components/Portal.vue b/vuejs/webapp/vuejs/src/components/Portal.vue index 3e95e39d7..6f2e6b77c 100644 --- a/vuejs/webapp/vuejs/src/components/Portal.vue +++ b/vuejs/webapp/vuejs/src/components/Portal.vue @@ -1,12 +1,10 @@ From 3bae71970bdff0280edb3082bb726a4a2f4e1aa3 Mon Sep 17 00:00:00 2001 From: holivier Date: Wed, 17 Jun 2020 19:41:28 +0200 Subject: [PATCH 13/24] themefrontjs: update jquery-min release in header.ftl (used by showselenium) --- themefrontjs/template/Header.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themefrontjs/template/Header.ftl b/themefrontjs/template/Header.ftl index 38832a630..cdf2f0597 100644 --- a/themefrontjs/template/Header.ftl +++ b/themefrontjs/template/Header.ftl @@ -61,7 +61,7 @@ under the License. --> - + <#if layoutSettings.styleSheets?has_content> From 9ec4584eda2966e74eb9175e8bb657b432b3b9c0 Mon Sep 17 00:00:00 2001 From: holivier Date: Sat, 20 Jun 2020 11:20:34 +0200 Subject: [PATCH 14/24] Add exampleFjs part of ofbiz-Resource-REST-reference --- .../src/docs/asciidoc/screenlet-example.adoc | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 examplefjs/src/docs/asciidoc/screenlet-example.adoc diff --git a/examplefjs/src/docs/asciidoc/screenlet-example.adoc b/examplefjs/src/docs/asciidoc/screenlet-example.adoc new file mode 100644 index 000000000..06c6b2029 --- /dev/null +++ b/examplefjs/src/docs/asciidoc/screenlet-example.adoc @@ -0,0 +1,134 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// += Example +== Category Example. +This category is for Example component. There is only one category for Example component. + +=== Find Examples +It's the standard search criteria form to be able to show a example list print. + +This screenlet update watcherName listExample. + +==== Screenlet parameters +* no + +==== Security rules +* nothing (component rules only) + + +=== Example List +List all examples according to the search criteria provided in watcherName ListExample + +In the list, exampleId is a link to select the example for all portlet watching watcherName showExample + +(the link update watcherName showExample) + +Click on link collapse the list. It's possible to un-collapse without doing a new search. + +==== Screenlet parameters +GenericRecapPageParam to give + +* recapPageId : give id to use as link to go to recap page for one example. + +==== Security rules +* nothing (component rules only) + + +=== DetailMenu for one example +For one example (exampleId is in the parameterized watcherName in the portal page) Show a summary, a link to the recap page and a menu to choose what +detail to show for this example in the ExampleDetail container. + +This screenlet is completely empty if there is no exampleId in watcherName. + +==== Screenlet parameters +* no + +==== Security rules +* nothing (component rules only) + + +=== Example Detail +Container used to show one detail about one example, one detail means a associated entity with example +(ex: show exampleFeature associated to the example or show exampleItems associated to the example or ...) + +This screenlet store which screenlet to show each time it's call, and will be updated each time the exampleId value +in the parameterized watcherName in the portal page is updated. + +This screenlet is used to define where show the detail. + +==== Screenlet parameters +* no + +==== Security rules +* nothing (component rules only) + + +=== Show Example +Show all example field for one example. + +It's possible to edit the example. When some modification is validated in this screenlet, watcherName exampleStatus is update. +So if screenlet ExampleStatus is in the same page and watch this watcherName a new call to the server is done for it. + +==== Screenlet parameters +* no + +==== Security rules +* nothing (component rules only) + + +=== Example Items +Lists items for one example and manage them, create new one, edit existing or delete it. + +This screenlet is completely empty if it is call with no exampleId. + +==== Screenlet parameters +GenericEditEditOrShowParam is used for parameters form so, + +* showEditButton : Y or N, show or not the edit button in items list for each item +* showScreenletMenu : Y or N, show or not edit menu in the screenlet bar, in this case the Add item button. + +==== Security rules +* nothing (component rules only) + + +=== Example status History +List all status change for the current example. + +This screenlet is completely empty if it is call with no exampleId. + +==== Screenlet parameters +* no + +==== Security rules +* nothing (component rules only) + + +=== Example Feature associated to a example +Lists ExampleFeatures associated for one example and manage association, create new one, edit existing or delete it. + +This screenlet contain a link to Create a new ExampleFeature out of this screenlet. + +This screenlet is completely empty if it is call with no exampleId + +==== Screenlet parameters +GenericEditEditOrShowParam is used for parameters form so, + +* showEditButton : Y or N, show or not the edit button in features list for each feature +* showScreenletMenu : Y or N, show or not edit menu in the screenlet bar, in this case the Add a new feature association. + +==== Security rules +* nothing (component rules only) + From 19a1ed333eceb3f6b1e2a3887f4eaeb2eb8db6d5 Mon Sep 17 00:00:00 2001 From: holivier Date: Thu, 2 Jul 2020 10:30:49 +0200 Subject: [PATCH 15/24] vuejs: start review of documentation, rename file and start change portal and portlet --- ...{poc-vuejs_en.adoc => vuejs-renderer.adoc} | 2 +- ...rontJsVueJs_en.adoc => frontJs-vuejs.adoc} | 49 ++++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) rename vuejs/src/docs/asciidoc/_include/{poc-vuejs_en.adoc => vuejs-renderer.adoc} (99%) rename vuejs/src/docs/asciidoc/{FrontJsVueJs_en.adoc => frontJs-vuejs.adoc} (52%) diff --git a/vuejs/src/docs/asciidoc/_include/poc-vuejs_en.adoc b/vuejs/src/docs/asciidoc/_include/vuejs-renderer.adoc similarity index 99% rename from vuejs/src/docs/asciidoc/_include/poc-vuejs_en.adoc rename to vuejs/src/docs/asciidoc/_include/vuejs-renderer.adoc index 73a1ef721..8ff30e625 100644 --- a/vuejs/src/docs/asciidoc/_include/poc-vuejs_en.adoc +++ b/vuejs/src/docs/asciidoc/_include/vuejs-renderer.adoc @@ -16,7 +16,7 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. //// -= POC Vuejs Renderer += Vue.Js Renderer Actually the first javascript application enabling to use portlet system - portal Apache OFBiz is written with the Vue.js framework. diff --git a/vuejs/src/docs/asciidoc/FrontJsVueJs_en.adoc b/vuejs/src/docs/asciidoc/frontJs-vuejs.adoc similarity index 52% rename from vuejs/src/docs/asciidoc/FrontJsVueJs_en.adoc rename to vuejs/src/docs/asciidoc/frontJs-vuejs.adoc index fd897455b..c33df681e 100644 --- a/vuejs/src/docs/asciidoc/FrontJsVueJs_en.adoc +++ b/vuejs/src/docs/asciidoc/frontJs-vuejs.adoc @@ -16,32 +16,41 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. //// -= FrontJs Portal += SPA / FrontJs with Vue.Js -frontJsPortal component included in frontJsPortal plugin aim to handle user interface with modern javascript framework -(Vue.Js, React, Angular, ect...). +SinglePageApplication or GUI done with a front done with a Javascript framework -It use portlet/portal system defined by XML and a javascript application. +done with *Vue.Js framework*, and *Vuetify Material-Design library* -This component is actually at POC (Proof Of Concept) state, so it aim to show some concrete ideas in order to discuss about them. + +vuejs component included a *FrontJs-Renderer* (similar as Macro-Renderer) usable to handle user interface with modern javascript +framework (Vue.Js, React, Angular, ect...) to be able to build a Single Page Application for one or more OFBiz component. + +It use the standard OFBiz Widget System defined by XML and a vuejs application. + +This component is currently at Work In Progress state, it aims to concretely build applications/components in order +to discuss the development process and the use of the application. + It is produced in an "Agility" spirit which mean that it focus to result in despite of some technical debt. +Currently there are still some TODO in the code and choice has been made to implement only xml tags and attributes which are used, +at least, in one application/component. + +XML tag or attribute not yet implemented generate warning or error. This documentation aim to, -* Explain the current state of the component, how it work and what points had been choose or took away. -* Prepare future documentation by explaining element meant for future users. +* Explain the current state of the component, how it work and which points had been choose or took away. +* Document how to use it to develop OFBiz application by explaining each specific item by example. -The two main goal of using a javascript framework for user interface is +The two main goal of using a javascript framework for user interface is 1. to have a "modern" look and feel * using recent GUI library - * usable in multiple context (PC, tablet, smartphone) + * usable in multiple context (PC, tablet, smartphone) 1. to increase interactive elements in screen, even if theses screens are based on standard modules : * update some part of the screen by action or by data update; * modify forms field as function of other field values; - * simpler screen configuration (portal page) without having to worry about portlets interaction. + * simpler screen configuration without having to worry about screenlet interaction. -include::_include/poc-vuejs_en.adoc[leveloffset=+1] +include::_include/vuejs-renderer.adoc[leveloffset=+1] == modular and generic UI Aiming to have an open and modular ERP, it's important that the user interface configuration allow to manage multiple screens @@ -52,22 +61,17 @@ Next chapters describe a user interface management system's POC aiming to respon include::Ui-POC.adoc[leveloffset=+2] -== Portlet -Portlet is an autonomous part of the screen, which mean that there can be action that only alter this part of the screen, +== Screenlet autonomous +in a modular approach, screenlet is an autonomous part of the screen, which mean that there can be action that only alter this part of the screen, mainly fired by itself. -Portlet allow a huge modularity gain in user interface, that way an user action (click on a link, type in a field, ect...) -must not precise the portlet itself but a logical name which can be subscribed one or more portlet. +Sreenlet allow a huge modularity gain in user interface, that way an user action (click on a link, type in a field, ect...) +must not precise the Sreenlet itself but a logical name which can be subscribed by one or more screenlet. -This logical name which is subscribed by portlets is called `watcherName`, this field is in the association table between -PortalPage and PortalPortlet. +This logical name which is subscribed by screenlet is called `watcherName`, this field is a new attribute for the `container` tag. -=== Portlet update -It's the update of the data stored in js client side store who trigger portlet render update. -== Portal Page - == FrontJs Glossary @@ -75,9 +79,6 @@ It's the update of the data stored in js client side store who trigger portlet r watchers:: They are objects stored in js store that can be altered remotely. watcher's subscribers update themselves with the new value when it change. -watchers:: c'est nom du store VueJs utilisé pour stocker les différentes variable sur lequel les container ou portletsont abonné -et donc se mette à jours quand celui-ci change. - [#WATCHERNAME] watcherName:: It is the key of a watcher From d0721109c550470c8a28424e725e82ba6f36678d Mon Sep 17 00:00:00 2001 From: ArkBash Date: Mon, 3 Aug 2020 17:43:45 +0200 Subject: [PATCH 16/24] Added th to VueDisplayField.vue table. --- vuejs/webapp/vuejs/src/components/VueDisplayField.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/vuejs/webapp/vuejs/src/components/VueDisplayField.vue b/vuejs/webapp/vuejs/src/components/VueDisplayField.vue index 5de38d198..39877e1be 100644 --- a/vuejs/webapp/vuejs/src/components/VueDisplayField.vue +++ b/vuejs/webapp/vuejs/src/components/VueDisplayField.vue @@ -2,6 +2,7 @@