From ef2a733d1045b820ce43be9ac2b6795815efe412 Mon Sep 17 00:00:00 2001 From: Abel Henry-Lapassat Date: Mon, 20 Jan 2025 14:32:59 +0100 Subject: [PATCH] UI Component text & images rework (latest javascript structures) --- .../LSN_40_ui-component/ui-component.md | 851 +++++++++++++----- .../welcome-card-object-form.png | Bin 75283 -> 75475 bytes .../welcome-card-permissions.png | Bin 39004 -> 42311 bytes .../welcome-card-resources.png | Bin 117000 -> 36495 bytes .../welcome-card-result.png | Bin 51967 -> 49886 bytes .../welcome-product-cards.png | Bin 86552 -> 353220 bytes 6 files changed, 618 insertions(+), 233 deletions(-) diff --git a/content/CTG_50_docs/CTG_25_front/LSN_40_ui-component/ui-component.md b/content/CTG_50_docs/CTG_25_front/LSN_40_ui-component/ui-component.md index de67b977..2cd81289 100644 --- a/content/CTG_50_docs/CTG_25_front/LSN_40_ui-component/ui-component.md +++ b/content/CTG_50_docs/CTG_25_front/LSN_40_ui-component/ui-component.md @@ -11,7 +11,7 @@ The need for custom widgets typically arises when existing Simplicité component > ***Note:*** Such requirements are often niche. Most technical operations can already be accomplished using Simplicité's core functionalities. Consequently, the primary purpose of creating custom widgets is to address unique visualization needs, enabling you to embed tailored styles and feature combinations within a custom interface. -### Our Example: Welcome Card +### Example: Welcome Card ![](welcome-card-result.png) @@ -33,7 +33,7 @@ Another important step is to grant your widget the rights of the module and view ![](welcome-card-permissions.png) -### Adding Content & Styles +### Creating Resources Then use the *Create Resources* action button, and click *Yes*. By doing so, you are adding 3 files to your object's *Resources* (visible in the bottom tab section "Resources"): @@ -41,138 +41,92 @@ Then use the *Create Resources* action button, and click *Yes*. By doing so, you - **STYLES**; the *CSS* file that serves as stylesheet for your object. There are no default style defined, only an empty bracket `#ext-obj { /* Custom styles */ }`. -- **CLASS**; the *Javascript* script that will be useful for the next lesson. +- **CLASS**; the *Javascript* script that will be useful for the next sections. ![](welcome-card-resources.png) > ***Note:*** The ressources are organized as any web element, in order to be easily integrated and created by designers and frontend developers. -For the welcome card, both content and style ressources are quite easy to create. Below are the *HTML* and *CSS* codes. +For the next steps, there are several ways to conceive your object: + +1) **Dynamic Instantiation** *(recommended)* + +As at some point you will have to fetch data and dynamically create your html elements (for example if you are retrieving an object and then using its data within a div). Thus it is more relevant to instantiate all of your HTML directly from your *CLASS* resource file using javascript. +This way is the one recommended by Simplicité when creating your External Objects, as they won't be very large in the DOM and mostly embedded in your existing Simplicité Interfaces, it is preferred to have the lowest loading time possible --through lighter HTML--, and overall the workflow of the *CLASS* and *HTML* resource file together is simpler using this way. + +This way your *HTML* resource file will remain very light and only contain sort of 'anchors' that will help you tell where and how to instantiate your different elements. And your *CLASS* file on the contrary, will be slightly bigger and complex. Below are shown the examples of the basic setup for our *DemoWelcomeCard* using this instantiation type: + +```html +
+ +
+
+ +
+``` + +```javascript +Simplicite.UI.ExternalObjects.DemoWelcomeCard = class extends Simplicite.UI.ExternalObject { + async render(params, data = {}) { + $('#demowelcomecard').append('Hello world!'); + } +} +``` + +* A class with the name of your External Object is declared within the `Simplicite.UI.ExternalObjects` module, extending the `Simplicite.UI.ExternalObject` to properly access any method from it. +* Only the `render(params, data)` function is declared yet, as it is the one called inside the server-side java code resource `com.simplicite.webapp.web.ResponsiveExternalObject`. +* We dynamically instantiate an "Hello world!" text simply to test that the connection & basic instantiation is enabled using `$('#demowelcomecard').append(...)`. + +The *HTML* will remain the same during this lesson (for this instantiation), while the *CLASS* will be modified and furtherly explained later on. + +> ***WARNING:*** The javascript code snippet is the one you'll have from V6.1.19, if you use an outdated version the resource file for javascript might be *SCRIPT* and then you can refer to the code presented at the very end of this document, that uses a more general and fully working setup; `var DemoWelcomeCard = DemoWelcomeCard || (function(){ ... })(jQuery);`. +
+V5 Default Javascript + +```javascript +var CustomWelcomeCard = CustomWelcomeCard || (function($) { + function render(url) { + $('#customwelcomecard').append('Hello world!'); + } + + return { render: render }; +})(jQuery); +``` +
+ +2) **Static Instantation** + +There are some use cases where it is easier to work with a static setup (i.e longer HTML), for example if you only fetch few data and have everything doable from HTML or from any "static function". This is thus not recommended compared to the previous version, as it will be slightly less effective while co-existing with/within Simplicité's solution. +With this method, the setup remains the same for the *CLASS* file, but you'll maybe already setup a more complete structure for your *HTML*: ```html
Welcome User - Welcome to Simplicité's solution! We're excited to have you onboard. Explore, interact, and enjoy a seamless experience tailored for you. + Welcome to Simplicité's solution! We're excited to have you onboard. Explore, interact, and enjoy your experience with us !
- - - + + +
``` -
-CSS Stylesheet - -```css -#customwelcomecard { - display: flex; - flex-direction: column; +Yet we won't actually instantiate the different features and functions, but still we can include them to know how they'll be included. Moreover, the presented scripts are not the final ones and will be modified with explanations later on this lesson. - width: 100%; - padding: 1rem 2rem; - - justify-content: start; - align-items: center; -} -.welcome-title { - width: 100%; - color: #303030; - text-align: center; - font-size: 3.5rem; - border-right: solid 0.25rem #5451FF; -} -.welcome-text { - padding: 0rem 1.5rem; - color: #474747; - text-align: left; - font-size: 2rem; - margin-bottom: 1rem; - border-right: solid 0.25rem #58EC9B; -} -.welcome-buttons { - display: flex; - flex-direction: row; - width: 100% - justify-content: center; - align-items: center; - gap: 3rem; - margin-top: 2rem; -} -.welcome-btn { - position: relative; - padding: 1.5rem 3rem; - border: none; - color: #303030; - background-color: transparent; - overflow: hidden; - cursor: pointer; - text-align: center; - font-size: 2rem; -} -.welcome-btn::before { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 0.25rem; - height: 100%; - background-color: transparent; - border-left: solid 0.25rem transparent; - border-bottom: solid 0.125rem transparent; - transition: all 0.5s ease; -} -.welcome-btn:hover::before { - border-left-width: 1.5rem; -} -.welcome-btn:active::before { - border-left-width: 3rem; -} -.tuto { - background-color: #FFF6E0; - transition: all 0.25s ease; - &:active { - background-color: darken(#FFF6E0,4%); - } - &::before { - border-color: #FFD166; - } -} -.info { - background-color: #FBEBFB; - transition: all 0.25s ease; - &:active { - background-color: darken(#FBEBFB,4%); - } - &::before { - border-color: #EC9DED; - } -} -.prd-nav { - background-color: #FED7D9; - transition: all 0.25s ease; - &:active { - background-color: darken(#FED7D9,4%); - } - &::before { - border-color: #FB3640; - } -} -``` -
+> ***Note:*** Both techniques are working fine in Simplicité and it indeed depends on how you prefer to do it. The main presented examples are gonna be for the first one, but at the end we provide a similar code that works with the second technique. ## Integration (Welcome Card) As for the *creation process*, the process of integrating the created widget is very straightforward, everything will be done in the *User Interface > Views > Show All*, select a view that is of type *Home Page*, and click the *Edit View* action button: -1 - Add a new **Sub-View**. -2 - Make it of type **External Page**. -3 - Select *External Object* as Source. -4 - Fill the *External Object* field with the name of your widget (for the welcome-card use **CustomWelcomeCard**). -5 - Save it, and you shall see a preview of your object integrated within the view. +1) Add a new **Sub-View**. +2) Make it of type **External Page**. +3) Select *External Object* as Source. +4) Fill the *External Object* field with the name of your widget (for the welcome-card use **CustomWelcomeCard**). +5) Save it, and you shall see a preview of your object integrated within the view. > ***Warning:*** Make sure to grant the rights for your external object ! > - If encountering the *External object ____ not granted*, first try to clear your cache. @@ -182,45 +136,116 @@ The creation of an object's behavior and interaction within one Simplicité appl ## Implementation (Welcome Card) -The first step is to make sure that our object can be aware of the Simplicité's system that he is a part f. To make such things we are gonna use the *javascript* resources of our External Object: **extobj_script** or **CLASS** or **SCRIPT**. +The first step is to make sure that our object can be aware of the Simplicité's system that he is a part of. To make such things we are gonna use the *javascript* resources of our External Object: **CLASS**. + +> ***Note:*** The **CLASS** resource file name is automatically assigned at creation from v6.1.19, above it's possible that your javascript resource file is named **SCRIPT**. Thus the inner workflow remains similar. ### File structure -The created *javascript* file initially contains only the base structure to later implement whatever we want in our object: +The default *CLASS* vows to be organized is a certain way while you develop it, this it is important to understand its structure and how to navigate through it: ```javascript -class CustomWelcomeCard extends Simplicite.UI.ExternalObject { +Simplicite.UI.ExternalObjects.DemoWelcomeCard = class extends Simplicite.UI.ExternalObject { async render(params, data = {}) { - $('#demowelcomecard').append('Hello world!'); + $('#demowelcomecard') + .append($('

').addClass("custom-header-title").text("Hello World!")) + .append($('

').addId("custom-result-text").text("none")) + .append($(' + +``` -From the previous example we just need a small improvement, adding the `let app= $ui.getApp()` line to instantiate a reference to the current Simplicité session: +3) ***Previous Script Structure*** +For the older *SCRIPT* the structure is thus more straigthforward, as you declare your whole object's script as an *Immediately Invoked Function Expression*, everything is contained in it, and for a proper organisation you can refer to the following script: ```javascript -var CustomWelcomeCard = (function(){ +var CustomWelcomeCard = CustomWelcomeCard || (function($){ let app = $ui.getApp(); - - return {}; -})(); + let login = $ui.getGrant().login; + let product = app.getBusinessObject("DemoProduct"); + + function foo() + { + console.log("Hello There !"); + } + + function asyncFoo() + { + product.search( function(){ + // access and manipulate the "DemoProduct" BusinessObject's instances here + }, null { inlineDocs: true }); + } + + return { + foo: foo, + asyncFoo: asyncFoo, + }; +})(jQuery); ``` +With such structure, you don't especially need the `render(params, data)` method to be called, and you can declare your variables as you would in a regular function. The only specificity is to actually return all of your functions and variables in order to access them in your *HTML*. + +Then accessing the returned methods in the *HTML* would be done similarly to how it is in the below code snippet: + +```html +
+

+ + +
+``` + +> ***Note:*** With this method you can't pass variables or constants statically, so you still need to add the fetched datas dynamically. + ### Manipulating Business Objects As they are a core element of any Simplicité application, it is important to know how to get and use them properly within any **External Object**. In order to do so you first need to have your app declared above, `let app = $ui.getApp();`, and then the method is `getBusinessObject(string name)`, thanks to which you can fetch all the Business Objects having the wanted `name`. @@ -272,44 +297,60 @@ And the final step to access your different objects from here is to know how the ![](demo-prd-fields-list.png) -Now here is how we implemented a simple product fetching & display function within the **CustomWelcomeCard** External Oject using the previously explained methods: +Now here is how we implemented a simple product fetching & display function within the **CustomWelcomeCard** External Oject using the previously explained methods, so inside the `BusinessObject.search(function(){ ... })` call we can use the following code snippet: ```javascript -function displayProductsWithin() +for (let i=0; i').addClass("demowelcomecard-product-card").on("click", () => { + // triggers an error but still saves & runs ... + $ui.displayForm(null, "DemoProduct", prd.row_id, { + nav: "add", + target: "work" + }); + }); + let cardLeft = $('
').addClass("dwc-product-card-left"); + + let cardLeftHeader = $('
').addClass("dwc-product-card-left-header"); + let cardLeftHeaderTitle = $('').addClass("dwc-product-card-left-header-title").text(prd.demoPrdName); + let cardLeftHeaderSubtitle = $('').addClass("dwc-product-card-left-header-subtitle").text(prd.demoPrdSupId__demoSupName+" - "+prd.demoPrdType); + + cardLeftHeader + .append(cardLeftHeaderTitle) + .append(cardLeftHeaderSubtitle); + + let cardLeftFooter = $('
').addClass("dwc-product-card-left-footer"); + let cardLeftFooterStock = $('').addClass("dwc-product-card-left-footer-stock").text(prd.demoPrdStock+" left in stock."); + let cardLeftFooterPrice = $('').addClass("dwc-product-card-left-footer-price").text(prd.demoPrdUnitPrice+"€"); + + cardLeftFooter + .append(cardLeftFooterStock) + .append(cardLeftFooterPrice); + + cardLeft + .append(cardLeftHeader) + .append(cardLeftFooter); + + let cardRight = $('
').addClass("demowelcomecard-product-card-right"); - productdBusinessObject.search( function() { //Here it is important to put the function here and not outside, so the search() operation is actually done before accessing the object, otherwise you might access a non-updated (empty) version of your object. - document.getElementById("welcome-list").hidden = false; - - console.dir(productdBusinessObject); - - for (let i=0; i -
-
-
- ${prd.demoPrdName} - ${prd.demoPrdType} -
- ${prd.demoPrdUnitPrice} -
-
- ${prd.demoPrdDescription} -
-
-
` - ); - } - }, null, {}); + let cardRightImage = $('').addClass("dwc-product-card-right-image").attr("src", imageSource).attr("alt", prd.demoPrdName); + let cardRightText = $('').addClass("dwc-product-card-right-description").text('"'+prd.demoPrdDescription+'"'); + + + cardRight + .append(cardRightImage) + .append(cardRightText); + productDiv + .append(cardLeft) + .append(cardRight); + + $("#demowelcomecard-productlist").append(productDiv); } + +$("#demowelcomecard-productlist").attr("hidden", "true"); // hiding by default ``` ![](welcome-product-cards.png) @@ -320,55 +361,94 @@ Additionaly we create the corresponding styles for the product card we are dynam CSS styles ```css -.welcome-product-card { - display: flex; - flex-direction: row; - width: 25%; - gap: 1rem; - padding: 2rem; - font-size: 2rem; - background-color: rgba(226, 226, 226, 0.24); - border-left: solid 0.125rem #E2E2E2; - box-shadow: none; - - transition: all 0.33s ease-in; -} -.welcome-product-card:hover { - transform: scale(1.05); - box-shadow: 0rem 0rem 0.5rem rgba(0,0,0,0.25); - border-left: solid 0.125rem #777777; -} -.welcome-prd-card-left { - display: flex; - flex-direction: column; -} -.prd-card-left-header { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: start; - margin-bottom: 0.5rem; -} -.prd-card-left-header-texts { - display: flex; - flex-direction: column; - text-align: left; -} -.card-left-header-prd-name { - font-size: 1.25rem; - font-weight: bold; -} -.card-left-header-prd-type { - font-size: 0.6rem; - font-style: italic; -} -.card-left-header-prd-price { - font-size: 0.75rem - text-align: right; -} -.prd-card-left-body { - text-align: left; - font-size: 1rem; +.demowelcomecard-product-card { + position: relative; + display: flex; + flex-direction: row; + box-sizing: border-box; + padding: 16px; + width: 40vw; /* fix this later on */ + border: solid 1px #C6C6C6; + background: rgba(198, 198, 198, 0.25); + & .dwc-product-card-left { + display: flex; + flex-direction: column; + justify-content: space-between; + width: 50%; + text-align: left; + & .dwc-product-card-left-header { + display: flex; + flex-direction: column; + & .dwc-product-card-left-header-title { + font-size: 32px; + font-weight: 600; + } + & .dwc-product-card-left-header-subtitle { + font-size: 24px; + } + } + & .dwc-product-card-left-footer { + display: flex; + flex-direction: column; + & .dwc-product-card-left-footer-stock { + font-size: 16px; + font-style: italic; + } + & .dwc-product-card-left-footer-price { + font-size: 32px; + font-style: italic; + } + } + } + & .demowelcomecard-product-card-right { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding-left: 16px; + width: 50%; + text-align: center; + border-left: solid 1px #C6C6C6; + transition: all 0.33s ease; + & .dwc-product-card-right-image { + display: flex; + justify-content: center; + align-items: center; + width: 75%; + height: auto; + } + & .dwc-product-card-right-description { + border-top: solid 1px #C6C6C6; + display: flex; + justify-content: center; + align-items: center; + padding: 16px; + font-size: 16px; + } + } + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 2px; + height: 100%; + border-left: solid 2px #777777; + transition: all 0.33s ease-in; + } + &:hover { + background: rgba(198, 198, 198, 0.5); + &::before { + border-left-width: 6px; + } + } + &:active { + background: rgba(251,54,64, 0.25); + &::before { + border-left-width: 6px; + border-left-color: #FB3640; + } + } } ``` @@ -382,23 +462,13 @@ Another interesting possibility is to implement custom shortcuts from our widget Such interactions can be done using the `BusinessObject.displayForm()` or `BusinessObject.displayList()` methods. The use case for displaying **Products' Form** is pretty straightforward: ```javascript -document.getElementById("welcome-list").insertAdjacentHTML( - 'beforeend', - `
- . . . ` -); - -// ... end of function & rest of code ... - -function goToProductForm(prdRowId) { - $ui.displayForm(null, "DemoProduct", prdRowId, { - nav: "add", - target: "work" - }); -} +$ui.displayForm(null, "DemoProduct", prd.row_id, { + nav: "add", + target: "work" +}); ``` -The `display` methods available for **Business Objects** are all working as follows; specify the name of the Business Object, the `row_id` of the specific instance of this object, and then some options as `nav` --that defines the behavior regarding the navigation element (breadcrumb)-- or `target` --that specifies the UI area in which the form will be displayed (`"work"` is the only appropriate for forms, lists etc)--. +The `display*` methods available for **Business Objects** are all working as follows; specify the name of the Business Object, the `row_id` of the specific instance of this object, and then some options as `nav` --that defines the behavior regarding the navigation element (breadcrumb)-- or `target` --that specifies the UI area in which the form will be displayed (`"work"` is the only appropriate for forms, lists etc)--. ### Getting User Infos @@ -408,17 +478,17 @@ Keeping the idea of redirecting our user, we will implement a slightly different let grant = $ui.getGrant(); // can be removed if using $grant let currentUserLogin = grant.login; // equivalent: $grant.login -let userBusinessObject = app.getBusinessObject("User"); +let user = app.getBusinessObject("User"); ``` Then thanks to the following script, we can easily use the previously fetched informations to properly display the currently logged user's form: ```javascript -userBusinessObject.search( function(){ - const user = userBusinessObject.list.find(u => u.usr_login === currentUserLogin); +user.search( function(){ + const usr = user.list.find(u => u.usr_login === currentUserLogin); - if (user && user.row_id) { - $ui.displayForm(null, "User", user.row_id, { + if (usr && usr.row_id) { + $ui.displayForm(null, "User", usr.row_id, { nav: "add", target: "work" }); @@ -428,10 +498,325 @@ userBusinessObject.search( function(){ }, null, {}); ``` -## Final Welcome-Card +## Final Welcome-Card (V6.1.19) After all that we should be done with the implementation of our customized Welcome-Card widget ! We so have 3 resource files that should look like this: +***HTML*** resource file: +```html +
+
+
+
+
+ +``` + +***CLASS*** resource file (script): +```javascript +Simplicite.UI.ExternalObjects.DemoWelcomeCard = class extends Simplicite.UI.ExternalObject { + async render(params, data = {}) + { + let app = $ui.getApp(); + let product = app.getBusinessObject("DemoProduct"); + let user = app.getBusinessObject("User"); + let login = $ui.getGrant().login; + + + $("#demowelcomecard-header") + .append($('

').text("Welcome to Simplicité's Demo !")) + .append($('

').text("We're excited to have you onboard. Explore, interact, and enjoy your experience with us !")); + + $("#demowelcomecard-actions") + .append($('