<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dumitru Corini</title>
    <description>The latest articles on DEV Community by Dumitru Corini (@dumitrucorini).</description>
    <link>https://dev.to/dumitrucorini</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F241995%2F9b83e657-f1bc-422e-9619-190c52ee4212.png</url>
      <title>DEV Community: Dumitru Corini</title>
      <link>https://dev.to/dumitrucorini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dumitrucorini"/>
    <language>en</language>
    <item>
      <title>Development of a UI Designer page as done at Bonitasoft, Part Two</title>
      <dc:creator>Dumitru Corini</dc:creator>
      <pubDate>Mon, 10 May 2021 10:37:12 +0000</pubDate>
      <link>https://dev.to/dumitrucorini/development-of-a-ui-designer-page-as-done-at-bonitasoft-part-two-3mjp</link>
      <guid>https://dev.to/dumitrucorini/development-of-a-ui-designer-page-as-done-at-bonitasoft-part-two-3mjp</guid>
      <description>&lt;p&gt;This article is a continuation of the article &lt;a href="https://dev.to/dumitrucorini/development-of-a-ui-designer-page-as-done-at-bonitasoft-bp5"&gt;Development of a UI Designer page as done at Bonitasoft, Part 1&lt;/a&gt; in which I talked about general page design choices, and then specifically about a list-styled page. I suggest reading that first before you read this.&lt;/p&gt;

&lt;p&gt;In this article, I will detail the &lt;em&gt;edit group&lt;/em&gt; modal. I chose to focus on this as it has most of the things you can find in other modals - and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modal general decisions
&lt;/h2&gt;

&lt;p&gt;To talk about modals, we first need to talk about the widget itself. The modal widget cannot be included inside another container in the whiteboard, which means that a UI Designer developer needs to choose where to put the container. When we started using the modal widget, our team decided to put modal widgets at the bottom of the editor, just as the UI Designer recommends.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embed modal containers
&lt;/h3&gt;

&lt;p&gt;Our team made a choice early in the development process to not allow a modal to be opened by another modal, so we had to look for other options when we had to decide how to edit something inside a modal. We wanted to simplify the user flow, because opening a modal inside another one raises questions about what would happen in the case of partial changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the page consistent with changes made inside a modal
&lt;/h3&gt;

&lt;p&gt;To keep the page consistent with the changes that are made inside a modal, we usually append a timestamp to the url that needs to be called. This way, if we update information in the groups list, all we have to do is modify the timestamp to trigger the API call again and get the recent changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opening and closing a modal
&lt;/h3&gt;

&lt;p&gt;Next up is our way of handling the closing and reopening of a modal. In the current UID state, there is no way of closing the modal easily since a modal can be closed in two different ways: with a button, and by clicking outside of it. Thus, there is no way to clean the modal state when closing the modal window. This causes problems when, for example, you have a modal state that is specific to an item; you close it and then open a modal for a second item. The state of the second modal will carry over some information from the first modal. Our solution was to clean the state of the modal upon opening. &lt;/p&gt;

&lt;h3&gt;
  
  
  Modal mock-up
&lt;/h3&gt;

&lt;p&gt;Since the &lt;em&gt;edit group&lt;/em&gt; modal is similar to the ones that we have already designed in other pages, we decided to not create a mock-up for this one in particular. Nonetheless, here’s a mockup of what we usually do for a modal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Xirc22JQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5y4xuri1d39t3bhi4u6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xirc22JQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5y4xuri1d39t3bhi4u6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Modal structure
&lt;/h3&gt;

&lt;p&gt;To style the modal and separate its parts, we use three different containers in the modal and add the css classes “modal-header”, “modal-body” and “modal-footer” to the top, middle and bottom containers. That lets us separate the modal into three sections as you can see on the image above. I should mention that these are bootstrap classes and thus we don’t need to create css specially for them. In rare cases, if needed, we can tweak the style to, for example, add a bigger edge on the sides of the modal. In the case of the &lt;em&gt;edit group&lt;/em&gt; modal, we didn’t change anything in the default bootstrap styles.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;em&gt;edit group&lt;/em&gt; modal
&lt;/h3&gt;

&lt;p&gt;This modal will be used by the end-user to modify a specific group. The fields that we let the end-user modify are the name, display name, description of the group, as well as the parent group.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement the &lt;em&gt;edit group&lt;/em&gt; modal
&lt;/h2&gt;

&lt;p&gt;As explained in &lt;a href="https://dev.to/dumitrucorini/development-of-a-ui-designer-page-as-done-at-bonitasoft-bp5"&gt;Development of a UI Designer page as done at Bonitasoft, Part 1&lt;/a&gt;, whenever we work on a feature we try to use up to four variables. In this case, we will only create two : &lt;code&gt;editGroupData&lt;/code&gt; and &lt;code&gt;editGroupCtrl&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;For the API call variable, we tried using one to get the information of the current parent, but as it was a bit too complex to manage, it became its own modular feature. I will talk about this feature later in the article.&lt;/p&gt;

&lt;p&gt;The fourth variable type is a handler. I’m going to talk about it later as well since we decided to merge it with handlers of other page features. &lt;/p&gt;

&lt;p&gt;And lastly, we have the two buttons at the bottom of the modal. I will talk about these at the end of the article. &lt;/p&gt;

&lt;h4&gt;
  
  
  How to open the modal
&lt;/h4&gt;

&lt;p&gt;To avoid problems with states that persist after reopening a modal, initialize the modal with the item to edit when the modal is opened. This is then passed to the modal when it opens. To do this, add a function in the controller &lt;code&gt;editGroupCtrl&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The function will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    initEditGroupModal: function($item) {
        $data.editGroupData.selectedGroup = $item;
        return "editGroupModal";
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also add the selected group to the data variable &lt;code&gt;editGroupData&lt;/code&gt;, which will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    selectedGroup: {}
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, use the &lt;code&gt;initEditGroupModal&lt;/code&gt; function in the edit button, in the repeatable container list created in part one. Set the action of the button to &lt;em&gt;Open modal&lt;/em&gt;, use an expression for the &lt;em&gt;Modal id&lt;/em&gt; property, and then use &lt;code&gt;editGroupCtrl.initEditGroupModal($item)&lt;/code&gt; as the value. Now, whenever we click the edit button in the list, it will call the &lt;code&gt;initEditGroupModal&lt;/code&gt; function and pass the row item from the list as a parameter.&lt;/p&gt;

&lt;p&gt;Use this selected group to show the display name of a group inside the &lt;code&gt;modal-header&lt;/code&gt; container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{"Edit group"|uiTranslate}} {{editGroupData.selectedGroup.displayName}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be good for the modal title.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit the group’s simple fields
&lt;/h3&gt;

&lt;p&gt;Since we want to make the modal the same as the one in the current portal, we will provide the end user with the ability to modify fields like name, display name, description and parent group. The first two will be simple input fields, and we decided to use a text area for the description since it would display better. I will go into detail about the parent group a bit later since modifying it is a bit more complex.&lt;/p&gt;

&lt;p&gt;Let’s backtrack a bit and talk about the editGroupData variable again and its use in the above mentioned fields. The variable will be used to keep the values of the input fields during the runtime. Thus, its updated value will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    name: "",
    displayName: "",
    description: "",
    selectedGroup: {}
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the editGroupData.name for the name input, and name the other inputs accordingly. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;initEditGroupModal&lt;/code&gt; function also needs to be updated to show the values of the display name, name and description when the user opens the modal. To do this, add these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$data.editGroupData.name = $item.name;
$data.editGroupData.displayName = $item.displayName;
$data.editGroupData.description = $item.description;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another way to do this would be to use the fields of the object &lt;code&gt;selectedGroup&lt;/code&gt; instead of three individual fields (name, displayName, description).&lt;/p&gt;

&lt;p&gt;Let’s talk about the parent group now. Since the API request for the group returns an id for the parent group and we want the display name, we will need to do an API call &lt;code&gt;currentParentUrl&lt;/code&gt; to get the information. Since the parent of our group is another group, the API call will look something like this: &lt;code&gt;../API/identity/group/{{editGroupData.selectedGroup.parent_group_id}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add an input field and use the &lt;code&gt;currentParentUrl.displayName&lt;/code&gt; to display the current parent group.&lt;/p&gt;

&lt;p&gt;You should have something like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MU8GuL5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bspi1sztabpzyo3cb1aq.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MU8GuL5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bspi1sztabpzyo3cb1aq.JPG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit the parent group
&lt;/h3&gt;

&lt;p&gt;What we have done so far is display the current parent group, and now we want to be able to edit it. Since we already made a decision to not open a modal by another modal, our team next decided to go with an input that has two states (view and edit). We also wanted to let the user be able to go back to the current parent group if they changed their mind midway through the choice of a new parent group.&lt;/p&gt;

&lt;p&gt;To make this modular, we created three variables (currentParentUrl, currentParentData and currentParentHandler). For the currentParentData, we used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return { 
    currentParentId: undefined,
    timestamp: 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also changed the currentParentUrl to &lt;code&gt;../API/identity/group/{{currentParentData.currentParentId}}?t={{currentParentData.timestamp}}&lt;/code&gt; and added &lt;code&gt;$data.currentParentData.currentParentId = $item.parent_group_id;&lt;/code&gt; to the &lt;code&gt;initEditGroupModal&lt;/code&gt; &lt;br&gt;
to reset the current parent when opening a new modal and &lt;code&gt;$data.currentParentData.timestamp = new Date().getTime();&lt;/code&gt; to get the information for the new parent. &lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;currentParentId&lt;/code&gt; is the same when we enter the modal,  there is no need for an API call to get the parent information.&lt;/p&gt;
&lt;h4&gt;
  
  
  Switch between the &lt;em&gt;view&lt;/em&gt; and &lt;em&gt;edit&lt;/em&gt; states
&lt;/h4&gt;

&lt;p&gt;Next add the second (edit) state, and a button next to each one of the inputs to switch between the two states. You should have something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DQA15CkE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtu0xj4nxt7bwrjb3edt.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DQA15CkE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtu0xj4nxt7bwrjb3edt.JPG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For these buttons to change the state, we will use a collection of actions. Whenever the end user clicks on one of the buttons, we will add a value into this collection. The handler will then catch and treat the action that was added. &lt;/p&gt;

&lt;p&gt;First add the array to the &lt;code&gt;currentParentData&lt;/code&gt;. We also need a variable that will keep the current state (editingParentGroup). After these additions, the data variable should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    currentParentId: undefined,
    actions: [],
    editingParentGroup: false
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, set the button actions to &lt;em&gt;Add to collection&lt;/em&gt;. The position in the array where you add the value doesn’t really matter, but the &lt;em&gt;value to add&lt;/em&gt; should be &lt;code&gt;edit&lt;/code&gt; for the first one and &lt;code&gt;cancel&lt;/code&gt; for the second one. &lt;/p&gt;

&lt;p&gt;Let’s talk about the last piece of the puzzle, the handler.&lt;/p&gt;

&lt;p&gt;Its value will be this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ($data.currentParentData.actions[0] === "edit") {
    $data.currentParentData.editingParentGroup = true;
    $data.currentParentData.actions = [];
}

if ($data.currentParentData.actions[0] === "cancel") {
    $data.currentParentData.editingParentGroup = false;
    $data.currentParentData.actions = [];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changes the state, and empties the actions array to execute the action only once. &lt;/p&gt;

&lt;p&gt;For these values to be taken into account, use this field in the two input field containers. Use &lt;code&gt;currentParentData.editingParentGroup&lt;/code&gt; as an expression for the hidden property value of the first button, and &lt;code&gt;!currentParentData.editingParentGroup&lt;/code&gt; for the second.&lt;/p&gt;

&lt;p&gt;Don’t forget to change the &lt;code&gt;initEditGroupModal&lt;/code&gt; function to reset the values of the above fields when the user opens the modal. Simply add these lines to the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$data.currentParentData.currentParentId=$item.parent_group_id;
$data.currentParentData.editingParentGroup = false;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Implement the edit state
&lt;/h4&gt;

&lt;p&gt;Now that you can change between the view and edit state, make the edit state work. As there is an autocomplete in the portal, we wanted to have something similar here. The catch is that the autocomplete widget that is provided in the UID doesn’t support returning an entire object - it only returns one field from the object. &lt;/p&gt;

&lt;p&gt;Returning an entire object is important since we want to have both the id of the selected item and the display name to display. (Note that there is a fix in the 2021.1 version of the UI Designer that will let you return an entire object. We will update the pages accordingly.)&lt;/p&gt;

&lt;p&gt;Since this fix is not yet here, the current implementation of a custom autocomplete uses a repeatable container. To do this, put the two parent group inputs into a single container and add another container for the autocomplete suggestions of possible parent groups. Display a text widget title with the value &lt;code&gt;&amp;lt;strong&amp;gt;{{"Name" | uiTranslate}}&amp;lt;/strong&amp;gt;&lt;/code&gt; to tell the end user to choose between the names of the parent groups. The container added under the &lt;em&gt;Name&lt;/em&gt; title will be a repeatable container and, thus, it will have one option per row, just like we did with the list in part one. We will set the value of the collection property for this container later.&lt;/p&gt;

&lt;p&gt;You should have something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rXQCVECy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/girrscobfu1399p082yq.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rXQCVECy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/girrscobfu1399p082yq.JPG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Get the list of possible parent groups
&lt;/h5&gt;

&lt;p&gt;Now, for the functional part. We created a full-fledged feature module here since it will also be used by the create group modal. &lt;/p&gt;

&lt;p&gt;The first variable is &lt;code&gt;parentDropdownData&lt;/code&gt;. Its value is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    searchParentValue: "",
    selectedParentDisplayName: undefined,
    selectedParentId: undefined,
    selectedParent: []
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;searchParentValue&lt;/code&gt; is used as the value of the input. The &lt;code&gt;selectedParent&lt;/code&gt; acts as a sort of action array when a parent is selected, from which &lt;code&gt;selectedParentDisplayName&lt;/code&gt; and &lt;code&gt;selectedParentId&lt;/code&gt; are extracted. &lt;/p&gt;

&lt;p&gt;We extract these because we found it easier to work with &lt;code&gt;$data.parentDropdownData.selectedParentDisplayName&lt;/code&gt; instead of &lt;code&gt;$data.parentDropdownData.selectedParent.displayName&lt;/code&gt;, but you can use the the second option if you want. &lt;/p&gt;

&lt;p&gt;Since we have the data, we can use &lt;code&gt;parentDropdownData.searchParentValue&lt;/code&gt; as the value of the edit state input.&lt;/p&gt;

&lt;p&gt;The second variable is the &lt;code&gt;parentDropdownUrl&lt;/code&gt;. Its value is &lt;code&gt;../API/identity/group?p=0&amp;amp;c=20&amp;amp;o=name&amp;amp;s={{parentDropdownData.searchParentValue}}&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;When the value is defined in the variable itself, an API call will be made when the page is opened and there will be no value for &lt;code&gt;parentDropdownData.searchParentValue&lt;/code&gt;. So to fix this, create the third variable, &lt;code&gt;parentDropdownCtrl&lt;/code&gt; and add a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getParentSearchUrl: function() {
    if ($data.parentDropdownData.searchParentValue &amp;amp;&amp;amp; 
        $data.parentDropdownData.selectedParentDisplayName !== 
        $data.parentDropdownData.searchParentValue) {
            return "../API/identity/group?p=0&amp;amp;c=20&amp;amp;o=name&amp;amp;s="+$data.parentDropdownData.searchParentValue;
    }
    return undefined;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an API call variable has &lt;em&gt;undefined&lt;/em&gt; in it’s value then the API call is not made. So, first check if there is a value in the input; if there is no value, there is no need to do an API call. Next check if the value entered is the same as the one that is selected, so the API call is not made again. &lt;/p&gt;

&lt;p&gt;(Imagine you want to look for &lt;em&gt;Acme&lt;/em&gt; and you type &lt;em&gt;A&lt;/em&gt;. An API call will be made to search for &lt;em&gt;A&lt;/em&gt;. When you select &lt;em&gt;Acme&lt;/em&gt; from the returned values, there is no need to redo an API call with &lt;em&gt;Acme.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;{{parentDropdownCtrl.getParentSearchUrl()}}&lt;/code&gt; as the value for &lt;code&gt;parentDropdownUrl&lt;/code&gt;.&lt;br&gt;
Now that there is an API call, there is another thing to consider before displaying the result. Displaying the full list of possible parents could introduce possible performance issues, so we went with displaying only 20 and then telling the user to type more. To do this, append the &lt;em&gt;Or type more&lt;/em&gt; to the list of 20 elements. We can add this to &lt;code&gt;parentDropdownCtrl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getFullSearchParentList: function() {
    var fullParentList = $data.parentDropdownUrl;
    if (fullParentList &amp;amp;&amp;amp; fullParentList.length == 20) {
        fullParentList.push({
            displayName: uiTranslate("Or type more...")
        });
    }
    return fullParentList;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To display the list of parents, use &lt;code&gt;parentDropdownCtrl.getFullSearchParentList()&lt;/code&gt; as the collection property value of the repeatable container.&lt;/p&gt;

&lt;h5&gt;
  
  
  Select a parent group from the list
&lt;/h5&gt;

&lt;p&gt;Now add a button in the repeatable container that, when clicked, triggers the selection of a parent group. To make it prettier, we went with a style property &lt;em&gt;link&lt;/em&gt;. Like the other action trigger, the button has &lt;em&gt;Add to collection&lt;/em&gt; as the action property. The collection would be &lt;code&gt;parentDropdownData.selectedParent&lt;/code&gt; and the value is an expression with the value &lt;code&gt;$item&lt;/code&gt;. This lets you add the selected parent group object into the array. For the label, use &lt;code&gt;{{$item.name}}&lt;/code&gt; to display the name of each item in the repeatable container. &lt;/p&gt;

&lt;p&gt;Then, create the fourth and last variable &lt;code&gt;parentDropdownHandler&lt;/code&gt;. Like the one for switching between the view/edit states, this one will catch a variable state, perform an action and clean the variable so the action is done only once. To do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ($data.parentDropdownData.selectedParent &amp;amp;&amp;amp; 
    $data.parentDropdownData.selectedParent.length &amp;gt; 0) {
        $data.parentDropdownData.searchParentValue=$data.parentDropdownData.selectedParent[0].displayName;
        $data.parentDropdownData.selectedParentDisplayName=$data.parentDropdownData.searchParentValue;
        $data.parentDropdownData.selectedParentId=$data.parentDropdownData.selectedParent[0].id;
        $data.parentDropdownData.selectedParent = [];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets the input field to the value selected, extracts the two fields that we want and cleans the selected parent, so the action is not re-triggered.&lt;/p&gt;

&lt;p&gt;Now for the finishing touches for the autocomplete. Since the user should not be able to click on &lt;em&gt;Or type more&lt;/em&gt;, add a function that will make the option unclickable. To add into the &lt;code&gt;parentDropdownCtrl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isSearchParentMore: function(item) {
    return item.name === uiTranslate("Or type more...");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this function by using a disabled expression with the value &lt;code&gt;parentDropdownCtrl.isSearchParentMore($item)&lt;/code&gt;. This will check if the item has the value of the field &lt;code&gt;name&lt;/code&gt; equal to &lt;em&gt;Or type more&lt;/em&gt; and disable it. &lt;/p&gt;

&lt;p&gt;If you run this, you should see a small problem: the dropdown is always visible. To fix this, use an expression for the hidden property of the repeatable container with the value &lt;code&gt;parentDropdownCtrl.hideDropdownMenu()&lt;/code&gt;. This function checks if there has been no API call response for the &lt;code&gt;parentDropdownUrl&lt;/code&gt;. If the response is empty (no parents are available with the current search value) or if the current search value is empty (nothing in the input):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hideDropdownMenu: function() {
    return !$data.parentDropdownUrl
           || $data.parentDropdownUrl.length === 0
           || $data.parentDropdownData.searchParentValue === "";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then clean the &lt;code&gt;parentDropdownUrl&lt;/code&gt; when a value is selected so the suggestion box disappears. Add &lt;code&gt;$data.parentDropdownUrl = undefined;&lt;/code&gt; to the &lt;code&gt;parentDropdownHandler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And you should be good for the autocomplete. The end result will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--743ES7-x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hnhffg02ocbzmbmbglkk.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--743ES7-x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hnhffg02ocbzmbmbglkk.JPG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Style of the autocomplete
&lt;/h5&gt;

&lt;p&gt;Before continuing, let’s look at the styling of what was just done. Let’s start by adding the css classes. If you look at the image above, the bottom container uses the class &lt;code&gt;dropdown dropdown-parent-group&lt;/code&gt;, while the container that displays the names of the possible parents uses &lt;code&gt;dropdown-menu&lt;/code&gt;. We use the &lt;code&gt;dropdown&lt;/code&gt; and &lt;code&gt;dropdown-menu&lt;/code&gt; classes from bootstrap and add &lt;code&gt;dropdown-parent-group&lt;/code&gt; to be able to override the default definition with a more specific CSS class. &lt;/p&gt;

&lt;p&gt;Also add this to the style.css file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.dropdown-parent-group .dropdown-menu {
    /* so that it stays on screen and avoids a scrollbar in the modal */
    position: relative;     
    display: block;
    /* to make it’s width the same as the input width*/
    width: 95%;
    /* to align it with the parent group input */
    margin-left: 15px;
    padding-left: 10px;
}

.dropdown-parent-group .dropdown-menu button {
    /* ellipsis */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: left;
    /* for it to not surpass the size of the entire container */
    width: 98%;
}

.dropdown-parent-group .dropdown-menu button:hover {
    /* so that it shows which item is selected */
    background: #2c3e50;
    color: #fff;
    /* so that some things are removed */
    text-decoration: none;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The action buttons
&lt;/h3&gt;

&lt;p&gt;There are two action buttons in the modal footer. Let’s look at the first one, the &lt;em&gt;Save&lt;/em&gt; button. This button will be used to make a PUT API call with the URL &lt;code&gt;../API/identity/group/{{currentParentData.currentParentId}}&lt;/code&gt;. Next, create the payload that will be sent when the end user clicks on the button. Create a function in &lt;code&gt;editGroupCtrl&lt;/code&gt; called &lt;code&gt;getEditGroupPayload&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getEditGroupPayload: function() {
        return {
            name: $data.editGroupData.name,
            displayName: $data.editGroupData.displayName,
            description: $data.editGroupData.description,
            parent_group_id:$data.parentDropdownData.selectedParentId
        };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will create a json object with the values that have been input by the end user. You  can now use this function (&lt;code&gt;editGroupCtrl.getEditGroupPayload()&lt;/code&gt;) in the &lt;em&gt;Data sent on click&lt;/em&gt; property of the button. Don’t forget to set it as an expression, since the value needs to be calculated instead of being interpreted as text. Two things should be done when we get a response. The first is to show an appropriate message depending on the response of the API call. The second is to refresh the list in the background.&lt;/p&gt;

&lt;p&gt;Tackle these one at a time. For the first one, add  &lt;code&gt;statusCode: “”&lt;/code&gt; to the &lt;code&gt;editGroupData&lt;/code&gt; and use it in the &lt;em&gt;HTTP status code&lt;/em&gt; property field (&lt;code&gt;editGroupData.statusCode&lt;/code&gt;). You can then add text fields that will be hidden if the value of the statusCode is different than the one for the message. &lt;/p&gt;

&lt;p&gt;For example, here are three uses of this variable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use in case of a success. The hidden value should be &lt;code&gt;editGroupData.statusCode !== 200&lt;/code&gt;. The message will be hidden if the API call response status code is not 200. &lt;/li&gt;
&lt;li&gt;Use in case of an error. The hidden value is &lt;code&gt;editGroupData.statusCode === "" || editGroupData.statusCode === 200&lt;/code&gt;. It will be hidden if there is no statusCode (when no API call was made) or when the API call is a success.&lt;/li&gt;
&lt;li&gt;Use in case of a specific error code, but where multiple errors are possible. For example, the group API returns a 403 if the user is not authorized to access the API and if a group already exists with the same name. To treat this, use a variable &lt;code&gt;failedResponse&lt;/code&gt; in the failed response value and a function &lt;code&gt;getErrorMessageFor403&lt;/code&gt; that will take the failed response and return an error message. It will look like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getErrorMessageFor403: function(failedResponse) {
    if (failedResponse
        &amp;amp;&amp;amp; (failedResponse.exception.indexOf(‘AlreadyExistsException’) &amp;gt; -1
        || failedResponse.message.indexOf('AlreadyExistsException') &amp;gt; -1)) {
            return uiTranslate("A group with the same name already exists.");
    }
    return uiTranslate("Access denied. For more information, check the log file.");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All this does is check if the error is of type &lt;em&gt;AlreadyExistsException&lt;/em&gt;, by checking if that text exists in the exception or the message of the failed response. The text can be customized as you please. To use it in a text widget, use&lt;br&gt;
&lt;code&gt;{{editGroupCtrl.getErrorMessageFor403(editGroupData.failedResponse)}}&lt;/code&gt; as the value.&lt;/p&gt;
&lt;h4&gt;
  
  
  Refresh the list of items after an API call
&lt;/h4&gt;

&lt;p&gt;Let’s backtrack a bit and talk about refreshing the list of groups that should happen behind the modal. Just as the &lt;code&gt;parentDropdownHandler&lt;/code&gt; treats the response of the API call for the possible parents, create a &lt;code&gt;refreshHandler&lt;/code&gt; that will allow editing a group. First, add a timestamp field in the &lt;code&gt;groupsData&lt;/code&gt; and add &lt;code&gt;&amp;amp;t={{groupsData.timestamp}}&lt;/code&gt; at the end of the &lt;code&gt;groupsUrl&lt;/code&gt;. As explained at the beginning of this article, updating this timestamp will trigger the API call again. The rest should be simple - when there is a successful API call for the save button, change the &lt;code&gt;groupsData.timestamp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ($data.editGroupData.statusCode === 200) {
    $data.groupsData.timestamp = new Date().getTime();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation has a huge flaw though: it will cause an infinite loop of refreshing the page when the group has been successfully edited. This is because the value of the field &lt;code&gt;statusCode&lt;/code&gt; in &lt;code&gt;editGroupData&lt;/code&gt; stays at 200, and the timestamp keeps changing every time there’s an action on the page (like, for example, an API call response that arrives). The easiest way that we found to fix this is to have an additional variable that will see if a group has been updated recently. Add &lt;code&gt;hasRecentlyBeenEdited&lt;/code&gt; to the &lt;code&gt;editGroupData&lt;/code&gt;. Change the &lt;code&gt;refreshHandler&lt;/code&gt; code to take this field into account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ($data.editGroupData.statusCode === 200 &amp;amp;&amp;amp; !$data.editGroupData.hasRecentlyBeenEdited) {
    $data.groupsData.timestamp = new Date().getTime();
    $data.editGroupData.hasRecentlyBeenEdited = true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, again, don’t forget to reset it’s value in the &lt;code&gt;initEditGroupModal&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$data.editGroupData.hasRecentlyBeenEdited = false;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can disable the button when the name value is empty. Add  &lt;code&gt;editGroupData.name === ""&lt;/code&gt; as an expression for the disabled property of the &lt;em&gt;Save&lt;/em&gt; button. You can also disable all the fields after an update by checking that the &lt;code&gt;hasRecentlyBeenEdited&lt;/code&gt; value is true.&lt;/p&gt;

&lt;p&gt;The last thing to consider for this modal is the cancellation button in the modal footer. If there is a successful update of the group, the label &lt;em&gt;Cancel&lt;/em&gt; might make the end user think the update could be cancelled, so change the label to &lt;em&gt;Close&lt;/em&gt;. This is done, once again, with the use of a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getChangeButtonLabel: function(statusCode) {
    return statusCode === 200 ? uiTranslate("Close") : uiTranslate("Cancel");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added this to the &lt;code&gt;groupsCtrl&lt;/code&gt; since it’s also used in other modals.&lt;/p&gt;

&lt;p&gt;This is how the full modal looks in the end on our side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mrVkyzG8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/arimqozu53d6tm6dpmp1.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mrVkyzG8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/arimqozu53d6tm6dpmp1.JPG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this in the preview:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EHEz1O_E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2mv6h4f9toc15n8t4dje.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EHEz1O_E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2mv6h4f9toc15n8t4dje.JPG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;So, now you know how we develop a modal with the UI Designer and you can do the same, with all the tips and tricks that we employed, like how to use a UID button to trigger an action on the page, how to create an autocomplete widget custom implementation, and how we treat errors using an API call response.&lt;/p&gt;

&lt;p&gt;And, as mentioned in Part 1, the next step is to create tests. You can find more information about how to test a Bonita UI Designer page in &lt;a href="https://dev.to/dumitrucorini/how-to-test-a-bonita-ui-designer-page-using-cypress-466e"&gt;this article&lt;/a&gt;. You can also find everything about this development and more in our &lt;a href="https://github.com/bonitasoft/bonita-web-pages/tree/dev/uid-pages"&gt;web pages project&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Development of a UI Designer page as done at Bonitasoft</title>
      <dc:creator>Dumitru Corini</dc:creator>
      <pubDate>Thu, 18 Feb 2021 15:28:38 +0000</pubDate>
      <link>https://dev.to/dumitrucorini/development-of-a-ui-designer-page-as-done-at-bonitasoft-bp5</link>
      <guid>https://dev.to/dumitrucorini/development-of-a-ui-designer-page-as-done-at-bonitasoft-bp5</guid>
      <description>&lt;p&gt;This article is intended to provide insight into the internal development process of a &lt;a href="https://documentation.bonitasoft.com/bonita//ui-designer-overview"&gt;user interface page&lt;/a&gt; and to explain some of the design choices that we have made at Bonitasoft. These page design choices should drive you towards more maintainable code and also give you some insight on best practices when creating Bonita UI Designer pages.&lt;/p&gt;

&lt;p&gt;In this article, I will provide a step-by-step explanation, with examples, of how user interface pages are developed by the Bonitasoft team. Our work is aimed at replacing some of the existing pages of the current Bonita Portal.&lt;/p&gt;

&lt;p&gt;Since we work in agile, our design choices are not set in stone - we update them when we decide it’s appropriate to do so, and then apply the changes to all the pages. But, generally speaking, the choices explained here are ones that apply to pretty much every page that we create.&lt;/p&gt;

&lt;p&gt;And before I start, I’ll note that if you are using a 2021.1 version of the Bonita development suite, you can import the pages we’re developing from the open source &lt;a href="https://github.com/bonitasoft/bonita-web-pages/tree/dev/uid-pages"&gt;web pages project&lt;/a&gt;. They can be used as examples, and customized to your own needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before starting development
&lt;/h2&gt;

&lt;p&gt;When we start developing a page, we first create a mockup of what we want the finished page to look like. This is mostly done in Paint.NET, and sometimes in Balsamiq, the Bonita UI Designer, or even using pen and paper. The benefits of specifying the page needs this way, before the development starts, fully outweigh the cost of the time and effort it takes to do this. For one thing, it lets us discuss the feasibility of new features. We have developed two types of pages that cover most use cases -  &lt;em&gt;list&lt;/em&gt; and &lt;em&gt;details&lt;/em&gt; - so we choose the appropriate page type (skeleton) at the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  General UI Designer standards
&lt;/h2&gt;

&lt;p&gt;There are three standards that we apply with each new page that are useful to know before we proceed.&lt;/p&gt;

&lt;p&gt;First, we always version our pages with a &lt;code&gt;V*&lt;/code&gt; suffix and update the version after each release. This way, someone that is external to Bonitasoft can import any page and modify it as they please, knowing that it won’t be overwritten when they upgrade to another version of the product.&lt;/p&gt;

&lt;p&gt;Next, it’s useful to understand the way we work with variables in the Bonita UI Designer. We always try to have four variables per feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The external API to retrieve the data&lt;/li&gt;
&lt;li&gt;A JavaScript variable that will hold the data - for example, the filter values or actions to be made&lt;/li&gt;
&lt;li&gt;A controller JavaScript variable containing utility functions or those that change the state of the data&lt;/li&gt;
&lt;li&gt;An event handler JavaScript variable. This listens to the data and prompts action depending on changes in it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then, there are the page offsets. Depending on the size of the screen, we use an offset of 0/1/1/2 for xs/sm/md/lg screen sizes. These were chosen for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first is the look and feel of the application. Our application uses AngularJS pages in addition to the UI Designer pages that we are developing. We wanted to keep a consistent look and feel between the two types of pages without having to change much in the AngularJS pages. &lt;/li&gt;
&lt;li&gt;The second reason comes from experimentation and is a subjective one. We tried 1/1/2/3 and 0/1/2/3 sizes, but neither of these looked as good to us as our current choice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I’ll illustrate the page design process, using the Groups page as an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of the &lt;em&gt;Groups&lt;/em&gt; page
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;Groups&lt;/em&gt; page is useful to integrate into an admin application, as its purpose is to show the groups of people in an organization. Some examples of groups might be the “North America” group or the “Research &amp;amp; Development” group. Each group will have a number of users associated with it, and might have sub-groups. For example, the North America group might have a Research &amp;amp; Development sub-group, and Walter Bates could be a member of that sub-group.  (The mockup for this page, shown below, was done with the help of Paint.NET.). &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UARqPXq_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixrhs0n2fxy64vjwlm64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UARqPXq_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixrhs0n2fxy64vjwlm64.png" alt="Mock-for-group-list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned above, we usually start by creating the skeleton of the page in the Bonita UI Designer. Since this is a page of the type &lt;em&gt;list&lt;/em&gt;, we will get a collection of items from the API call and then use the repeatable container to display the information for each item. Getting the information in the Bonita UI Designer is easy and straightforward. We create an external API variable, called &lt;code&gt;groupsUrl&lt;/code&gt; with the value &lt;code&gt;../API/identity/group?c=10&amp;amp;p=0&amp;amp;o=displayName ASC&lt;/code&gt;. This is everything needed to get the information from the API and save it in the &lt;code&gt;groupsUrl&lt;/code&gt; variable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Displaying the list of groups
&lt;/h3&gt;

&lt;p&gt;Now of course we want to display this information. As mentioned above, our team usually uses page offsets. To apply the aforementioned offsets, the current solution is to drag and drop a container, change its size to 1, add a second container next to it, change its size to 10, and then add a third container to its right. This will create offsets of 1 for the page. Next, go through each one of the xs/sm/md/lg sizes and change the left and right containers accordingly.&lt;/p&gt;

&lt;p&gt;For the data, we can drop a container in the middle container and use &lt;code&gt;groupsUrl&lt;/code&gt; as a value in the collection property of the new container. For each item in the list to have a delimiter, we can add &lt;code&gt;group-item&lt;/code&gt; as a CSS class for this container. For the fields to display, from the mock file, we see from the mockup that we want to show the data for &lt;em&gt;Display name&lt;/em&gt;, &lt;em&gt;Name&lt;/em&gt;, &lt;em&gt;Parent group&lt;/em&gt;, &lt;em&gt;Created on&lt;/em&gt; and &lt;em&gt;Updated on&lt;/em&gt;. We also want the user to see the description at the bottom of each item. Since we will also have some buttons to the right, we start by adding a container that has size 9 inside the repeatable container. We can drop an additional container to the right of it in which we will put the buttons later.&lt;/p&gt;

&lt;p&gt;For readability purposes, we usually put each field in a separate container. The sizes for each field is for you to define; we went with 2, 2, 3, 2, 3 because we thought it looked good on the page. In each container, we added two text widgets: the first acts as a label, and the second holds the value. These also have &lt;code&gt;item-label&lt;/code&gt; and &lt;code&gt;item-value&lt;/code&gt; CSS classes respectively so they are styled accordingly. The item labels are taken from the mockup image. They can be simply written as the text widget values. For the item values, we usually look at the API call to know which json object property to display, and we use the standard angularjs interpolation. For example, since we want to display the name of a group, we use &lt;code&gt;{{$item.name}}&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;There are three special cases that don’t follow this &lt;code&gt;item-label/item-value&lt;/code&gt; formula. &lt;/p&gt;

&lt;p&gt;The first is when trying to display a date. Even though dates have the &lt;code&gt;item-label/item-value&lt;/code&gt; CSS classes, the values need to use a specific formatting. This is done by using the uiDate AngularJs directive, so the value of the &lt;code&gt;item-value&lt;/code&gt; text widget will be &lt;code&gt;{{$item.creation_date.replace(" ", "T") | uiDate:'short'}}&lt;/code&gt;, which formats the creation date here with the short style. &lt;/p&gt;

&lt;p&gt;The second specific case is the description. As we can see on the mockup, there is no item label, and the third and fourth group have no description. To create this we add two text fields with the &lt;code&gt;item-label&lt;/code&gt; CSS class. The first text field has the value &lt;code&gt;{{$item.description}}&lt;/code&gt;, and the second one has &lt;code&gt;No description&lt;/code&gt;. We can now use the hidden property on each of the text widgets to display depending if the description property is empty.&lt;/p&gt;

&lt;p&gt;The third specific case is the parent group. As we can see on the mockup for the first group, the parent group value is &lt;code&gt;--&lt;/code&gt;. We will need a function to know if there is no parent group. As I said earlier, our team usually puts this kind of function in a controller variable. So, we create the &lt;code&gt;groupsCtrl&lt;/code&gt; variable with the value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    isInformationEmpty(information) {
       if (!information) {
           return "--";
       }
       return information;
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then use this function in the item value field for the parent group. &lt;/p&gt;

&lt;p&gt;Another important thing to note about this parent group is that it has the potential to have a really long value. We use a tooltip to display the entire value, so the value for this field would be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div title="{{groupsCtrl.isInformationEmpty($item.parent_path)}}"&amp;gt;
   {{groupsCtrl.isInformationEmpty($item.parent_path)}}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To style everything, we went with this CSS, which not only creates a delimiter between each item, but also creates specific styles for the item values and the item labels. The last thing it does is create a hover effect on each item.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.group-item {
    border-bottom: 1px solid lightgray;
    overflow: hidden;
    transition: 0.3s;
    padding-top: 0.5rem;
}

.group-item p  {
    margin-bottom: 0;
}

.group-item:hover {
    box-shadow: 0 0 5px;
    margin-top: 0px;
}

.item-label p,
.item-label.component {
    font-size: 11px;
    opacity: 0.7;
    margin: 0;
    padding-right: 0;
    word-wrap: break-word;
    word-break: break-word;
}

.item-value,
.item-value a,
.item-value p {
    font-size: 12px;
    margin: 0 0 3px 0;
    padding-right: 0;
    word-wrap: break-word;
    word-break: break-word;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end, it looks something like this in the Bonita UI Designer whiteboard for a &lt;em&gt;lg&lt;/em&gt; screen.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X3Mf5dOO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apm3luv3itcy2xshuxon.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X3Mf5dOO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apm3luv3itcy2xshuxon.JPG" alt="Whiteboard1"&gt;&lt;/a&gt;&lt;br&gt;
With something like this in the preview.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LCRAGlIx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfcnh1s1nnq5hvm6756e.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LCRAGlIx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfcnh1s1nnq5hvm6756e.JPG" alt="Preview1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After displaying the list of groups, what remains to do are the filters, the &lt;em&gt;load more items&lt;/em&gt; button and the action buttons. There are also still some small improvements to do to the page itself, like adding a title, showing the number of displayed items for when we will use &lt;em&gt;load more&lt;/em&gt; and also adding a message to display when there are no groups available.&lt;/p&gt;

&lt;p&gt;We add the buttons first, and will implement them later. For the three small improvements, they are pretty straightforward, so I will not go into detail here. Just keep in mind that after we finish the &lt;em&gt;load more&lt;/em&gt; button, there will be some small changes to when we display the number of items shown and the no more groups message.&lt;/p&gt;
&lt;h3&gt;
  
  
  Filters
&lt;/h3&gt;

&lt;p&gt;Let’s start with the filters and specifically, with the data that we need to create for the filters. We can add a new Javascript variable, &lt;code&gt;groupsData&lt;/code&gt;, that will contain the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    sortByOptions: [   
        { displayKey: "Display name (Asc)", returnKey: "displayName+ASC" },
        { displayKey: "Display name (Desc)", returnKey: "displayName+DESC" },
        { displayKey: "Name (Asc)", returnKey: "name+ASC" },
        { displayKey: "Name (Desc)", returnKey: "name+DESC" }
    ],
    filters: {
        sortOrder: "",
        searchValue: ""
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sortByOptions&lt;/code&gt; array contains the “sort by” possibilities. As you can see, each object contains a &lt;code&gt;displayKey&lt;/code&gt; and a &lt;code&gt;returnKey&lt;/code&gt;. The first is what the user will see, and the second is what will return from the select box when the user chooses the value. The &lt;code&gt;filters&lt;/code&gt; object contains the values of the sort select box and the search input. We usually add a container for the filters and a CSS class to leave a small space between the title and the filters. With this data created, we can now add the two filters. To take into account the values of the filters, we create a function that will use the filter values. We also need to use this function in the external API. Add the following to the &lt;code&gt;groupsCtrl&lt;/code&gt; variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;groupsQueryParameters: function() {
    var queryParameters = "c=10&amp;amp;p=0”;
    if ($data.groupsData.filters.sortOrder) {
        queryParameters += "&amp;amp;o=" + $data.groupsData.filters.sortOrder;
    } else {
        queryParameters += "&amp;amp;o=displayName ASC";
    }
    if ($data.groupsData.filters.searchValue) {
        queryParameters += "&amp;amp;s=" + $data.groupsData.filters.searchValue;
    }
    return queryParameters;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then use the function in the external API, with the new value for &lt;code&gt;groupsUrl&lt;/code&gt; : &lt;code&gt;../API/identity/group?{{groupsCtrl.groupsQueryParameters()}}&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Load more items button
&lt;/h3&gt;

&lt;p&gt;The current implementation of the &lt;em&gt;load more&lt;/em&gt; button follows a specific algorithm. When the page is loaded, there is an API call for 20 items (2 pages of groups). If there are more than 20 groups, we will get 10 items (the third page of groups) as a result of the button click, but the second page from the first API call is displayed. Clicking the button again, we will do a third API call to get 10 items and display the 10 items of the second API call. &lt;/p&gt;

&lt;p&gt;To know when to disable the button, we need to be able to display a page from memory and get the next page of items from the API. When there are no more results in the API, we will know that the button should be disabled. To implement this load more button, we found that we need at least 4 variables.&lt;/p&gt;

&lt;p&gt;We also created 2 variables that we found useful, but are not absolutely necessary.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the 4 variables that are necessary: &lt;/li&gt;
&lt;li&gt;An array that will keep the groups that are displayed currently&lt;/li&gt;
&lt;li&gt;An array with the result from the API for page 3 and after&lt;/li&gt;
&lt;li&gt;An array that keeps the next results to concatenate to the current displayed ones&lt;/li&gt;
&lt;li&gt;A variable that keeps the current page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, the 2 other variables that are useful are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One that keeps the number of groups to request per page in case we want to switch to another number&lt;/li&gt;
&lt;li&gt;One that saves the number of groups that we will have for the page in the memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, in the end, our &lt;code&gt;groupsData&lt;/code&gt; will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    groupsInMemory: [],
    pageId: 0,
    groupsFromAPI: undefined,
    displayedGroups: [],
    numberOfGroupsPerPage: 10,
    numberOfgroupsInMemory: 0,
    sortByOptions: [   
            { displayKey: "Display name (Asc)", returnKey: "displayName+ASC" },
            { displayKey: "Display name (Desc)", returnKey: "displayName+DESC" },
            { displayKey: "Name (Asc)", returnKey: "name+ASC" },
            { displayKey: "Name (Desc)", returnKey: "name+DESC" }
    ],
    filters: {
            sortOrder: "",
            searchValue: ""
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  The implementation
&lt;/h5&gt;

&lt;p&gt;Let’s start by setting up the API calls. To keep them generic, we modify the query parameters function in the controller by passing the page number. At the start of the page it will be 0 and we should get 20 elements. After the first call, we pass a page number to the query parameters function. The function in the end should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;groupsQueryParameters: function(pageNumber) {
        var groupsPerPage = $data.groupsData.numberOfGroupsPerPage;
    if (pageNumber === 0) {
        //on first call we need to load two pages
        groupsPerPage *= 2;
    }
    var queryParameters = "c=" + groupsPerPage + "&amp;amp;p=" + pageNumber;
    if ($data.groupsData.filters.sortOrder) {
        queryParameters += "&amp;amp;o=" + $data.groupsData.filters.sortOrder;
    } else {
        queryParameters += "&amp;amp;o=displayName ASC";
    }
    if ($data.groupsData.filters.searchValue) {
        queryParameters += "&amp;amp;s=" + $data.groupsData.filters.searchValue;
    }
    return queryParameters;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to consider two more things: the &lt;em&gt;load more&lt;/em&gt; button itself and the event handler. &lt;/p&gt;

&lt;p&gt;The load more button should do an API call to get the groups after the first page, thus it will have &lt;code&gt;../API/identity/group?{{groupsCtrl.groupsQueryParameters(groupsData.pageId)}}&lt;/code&gt; as the url to call. The successful response will be stored in the &lt;code&gt;groupsData.groupsFromAPI&lt;/code&gt; array. The button should be disabled when the number of groups displayed cannot be divided by the number of groups per page. For example, if there are 24 elements, there will be 4 groups left after the second page is shown.There is no fourth page. So after showing the third, we can disable the button. We also disable the button if there are no groups in the memory. So, the condition is: &lt;code&gt;groupsData.displayedGroups.length % groupsData.numberOfGroupsPerPage &amp;gt; 0 || groupsData.numberOfgroupsInMemory === 0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We also want to hide the button if the &lt;code&gt;displayedGroup&lt;/code&gt; array is empty, so we need to take into account if there are any groups to display. So, for the “groups shown” and the “no group to display” fields, we need to verify if the &lt;code&gt;displayedGroup&lt;/code&gt; array contains data.&lt;/p&gt;

&lt;p&gt;To complete the list feature, we configure the &lt;em&gt;load more&lt;/em&gt; handler. There are three different data states which trigger it: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the user first arrives on the page&lt;/li&gt;
&lt;li&gt;When the user clicks on the &lt;em&gt;load more&lt;/em&gt; button and there is another page to display&lt;/li&gt;
&lt;li&gt;When the user clicks on the button and there are no more pages, but there is still something in the memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s tackle each one of these separately. After we get the first 2 pages from the API, we save the first page in the &lt;code&gt;displayedGroup&lt;/code&gt; array and save the second page in the memory in &lt;code&gt;groupsInMemory&lt;/code&gt;. We also set the number of groups for the next page to be equal to the size of &lt;code&gt;groupsInMemory&lt;/code&gt;. Then we set the pageId to 2 in order to get the third page the next time we do the API call, and to set the &lt;code&gt;groupsUrl&lt;/code&gt; to “empty” in order to do all of this only once. During the execution, we don’t need to reuse the &lt;code&gt;groupsUrl&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The second part of the &lt;em&gt;load more&lt;/em&gt; handler should add the page from memory to the displayed list of groups, and add the page returned by the API to the memory. We should also increment the pageId so the next API call will request the next page. Lastly, we make the result from the API request empty so this gets executed only once. &lt;/p&gt;

&lt;p&gt;The third part of the &lt;em&gt;load more&lt;/em&gt; handler adds the results from the memory, and then cleans those results from the memory, so we don’t execute the case multiple times.&lt;/p&gt;

&lt;p&gt;In the end, the handler looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if($data.groupsUrl) {
    $data.groupsData.displayedGroups = $data.groupsUrl.slice(0, $data.groupsData.numberOfGroupsPerPage);
    $data.groupsData.pageId = 2;
    $data.groupsData.groupsInMemory = $data.groupsUrl.slice($data.groupsData.numberOfGroupsPerPage);
    $data.groupsData.numberOfGroupsInMemory = $data.groupsData.groupsInMemory.length;
    $data.groupsUrl = undefined;
}

if($data.groupsData.groupsFromAPI &amp;amp;&amp;amp; $data.groupsData.groupsFromAPI.length) {
    $data.groupsData.displayedGroups = $data.groupsData.displayedGroups.concat($data.groupsData.groupsInMemory);
    $data.groupsData.pageId++;
    $data.groupsData.groupsInMemory = $data.groupsData.groupsFromAPI;
    $data.groupsData.numberOfGroupsInMemory = $data.groupsData.groupsInMemory.length;
    $data.groupsData.groupsFromAPI = undefined;
}

if($data.groupsData.numberOfGroupsInMemory &amp;gt; 0 &amp;amp;&amp;amp; $data.groupsData.groupsFromAPI &amp;amp;&amp;amp; $data.groupsData.groupsFromAPI.length === 0) {
    $data.groupsData.displayedGroups = $data.groupsData.displayedGroups.concat($data.groupsData.groupsInMemory);
    $data.groupsData.groupsInMemory = undefined;
    $data.groupsData.numberOfGroupsInMemory = 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The end product looks something like this in the Bonita UI Designer whiteboard:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w3IqAnQ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9eu0mrjodzggx7v34pdf.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w3IqAnQ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9eu0mrjodzggx7v34pdf.JPG" alt="Whiteboard2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And something like this in the preview:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1kXebSiw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rac7ayyabthfzov1oobh.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1kXebSiw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rac7ayyabthfzov1oobh.JPG" alt="Preview2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You now know how to develop a list styled page as us, at Bonitasoft do it! &lt;/p&gt;

&lt;p&gt;And of course the next thing to do - which is very important - is to test the page. This article does not go into that, but you can find out more about how to test a Bonita UI Designer page using Cypress in &lt;a href="https://dev.to/dumitrucorini/how-to-test-a-bonita-ui-designer-page-using-cypress-466e"&gt;this article&lt;/a&gt;. You can find the page I’ve described here, as well as others that were developed and tested, in the &lt;a href="https://github.com/bonitasoft/bonita-web-pages/tree/dev/uid-pages"&gt;web-pages&lt;/a&gt; Github repository.&lt;/p&gt;

&lt;p&gt;Thank you for reading and stay tuned for the next article about how the R&amp;amp;D at Bonitasoft develops the modals, which will be useful for modifying the list of groups.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to test a Bonita UI Designer page using Cypress</title>
      <dc:creator>Dumitru Corini</dc:creator>
      <pubDate>Tue, 15 Sep 2020 07:21:58 +0000</pubDate>
      <link>https://dev.to/dumitrucorini/how-to-test-a-bonita-ui-designer-page-using-cypress-466e</link>
      <guid>https://dev.to/dumitrucorini/how-to-test-a-bonita-ui-designer-page-using-cypress-466e</guid>
      <description>&lt;p&gt;Once you’ve made a Bonita UI Designer page, testing it is very important.&lt;/p&gt;

&lt;p&gt;To test pages made with the Bonita UI Designer, our team decided to move towards an e2e framework that is fast to set up and allows us to write tests efficiently.&lt;/p&gt;

&lt;p&gt;We decided to use &lt;a href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt;, an open-source, Javascript framework that lets you write Given-When-Then semi-structured tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To fully grasp the power of this article, you need to familiarize yourself with a couple of technologies. &lt;/p&gt;

&lt;p&gt;First, this article uses the &lt;a href="https://documentation.bonitasoft.com/bonita/7.11/ui-designer-overview"&gt;Bonita UI Designer&lt;/a&gt; to create a simple page that lets you register a user. &lt;/p&gt;

&lt;p&gt;Then, this article gives two test use cases of Cypress, without diving into the full potential of the tool. If you are interested in uncovering its other possibilities, I suggest checking out their &lt;a href="https://docs.cypress.io"&gt;documentation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You can find everything discussed in this article in the &lt;a href="https://github.com/bonitasoft-labs/bonita-uid-page-cypress-test"&gt;github&lt;/a&gt; repository.&lt;br&gt;
The two tests that I will talk about aren’t the only ones that you will find in the repository, so if you want to see more examples, you can clone the repository and then check them out.&lt;/p&gt;
&lt;h3&gt;
  
  
  Diving into the page structure
&lt;/h3&gt;

&lt;p&gt;Let’s start with the page. It has a form container with multiple fields that will ask for different information in order to create a new user. You can see some messages that will be displayed in case of errors and success, as well as a loading message. There is also a hidden functionality that makes the success or error messages disappear after three seconds if the user takes any action on the page. We usually use this feature in order to mimic a toast and thus not clog the user's screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qLE5Gcd9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/bonitasoft-labs/bonita-uid-page-cypress-articles/master/uid-pages/page-with-registration-form/article/images/page.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qLE5Gcd9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/bonitasoft-labs/bonita-uid-page-cypress-articles/master/uid-pages/page-with-registration-form/article/images/page.jpg" alt="Image of the page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The test choices
&lt;/h2&gt;

&lt;p&gt;Without further ado, let’s talk about tests. &lt;/p&gt;

&lt;p&gt;Cypress tests are made from two distinct parts. The first is about writing the test scenarios in one file and the second is about explaining with code what each line of the scenario will do.&lt;br&gt;
The cypress-cucumber-preprocessor library is used in order to write scenarios in the Given-when-then style.&lt;/p&gt;

&lt;p&gt;The two tests that I am going to talk about are :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A failure in the creation of the user because the passwords don’t match&lt;/li&gt;
&lt;li&gt;The loader being displayed while the user is being created&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose the first because it is a test that we have done and redone multiple times in our pages, but with a little subtlety. In most of the cases, we are checking that a correct API call is made, with the correct parameters and the correct response. In this case, none of that is necessary because the test for password equality is made client-side. Thus the additional thing that is checked is if no API call is made because none is needed.&lt;/p&gt;

&lt;p&gt;The second was chosen because it depicts an use-case that seemed pretty interesting to me, and also because the building blocks of the scenario might be useful in a variety of situations, as, for example, the test for checking if the message disappears after three seconds.&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing user creation failure because the password is not the same
&lt;/h2&gt;

&lt;p&gt;Let’s talk about the first test scenario.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Given The server is started
And API call response for "user creation should not be called" is mocked
When I visit the index page
And All inputs are filled
And I modify the password field
And I try to create the user
Then I see the message about "Passwords don't match."
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The idea for this test is to both verify that there is no API call that is made to the user API, and also that there is a message saying that passwords don't match.&lt;/p&gt;

&lt;p&gt;To verify that there is no API call that is made, we can make a route to the user API without actually creating a response, but only by throwing an error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.route({
   method: "POST",
   url: userAPI,
   onRequest: () =&amp;gt; {
       throw new Error("This should have not been called");
   }
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In order to verify that everything works according to plan, we visit the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.visit(url);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After that, we fill all inputs with information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.get("input").eq(0).type("walter.bates");
cy.get("input").eq(1).type("bpm");
cy.get("input").eq(2).type("bpm");
cy.get("input").eq(3).type("Walter");
cy.get("input").eq(4).type("Bates");
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Followed by modifying the password to not match the confirm password value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.get("input").eq(1).type("incorrect");
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And the last action on the page being that of creating the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.contains("button", "Create").click();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After we have setup this specific situation, we want to test that the "Passwords don't match." message is present on the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.contains("p", message).should("be.visible");
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After launching the test runner, we can see that the test is executed and is successful.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tq_WIx4T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/bonitasoft-labs/bonita-uid-page-cypress-articles/master/uid-pages/page-with-registration-form/article/images/test_passwords_dont_match.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tq_WIx4T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/bonitasoft-labs/bonita-uid-page-cypress-articles/master/uid-pages/page-with-registration-form/article/images/test_passwords_dont_match.jpg" alt="test passwords not matching"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing the loader being displayed while the user is being created
&lt;/h2&gt;

&lt;p&gt;The scenario for this test is based on the display of a loader and checking that it disappears after five seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Given The server is started
And API call response for "user created after delay" is mocked
When I visit the index page
And All inputs are filled
And I try to create the user
Then The loader is shown
When I wait for 5000 delay
Then I see the message about "User successfully created."
And The loader is not shown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, some parts of this test are the same as in the first test. The main things that are different are the definition of the API mock, checking that the loader is shown, waiting for 5 seconds and checking the end-state of the page.&lt;/p&gt;

&lt;p&gt;Mocking the API response after 5 seconds lets us have a moment during which the response has not arrived from the user API and thus the loading would be shown.&lt;/p&gt;

&lt;p&gt;The response is the same as the one that you would get from the Portal, which lets us be very close to reality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.fixture("json/userCreatedResponse.json").as("userCreated");
cy.route({
   method: "POST",
   url: userAPI,
   delay: 5000,
   response: "@userCreated"
}).as("userCreatedRoute");
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;While the user is created, we verify that the loader is shown.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.get(".glyphicon.glyphicon-cog.gly-spin").should("be.visible");
cy.contains("p", "Creating user.").should("be.visible");
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After which we wait for 5 seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.wait(time);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And after 5 seconds, when the response arrives, we check that the message disappears.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.get(".glyphicon.glyphicon-cog.gly-spin").should("not.be.visible");
cy.contains("p", "Creating user.").should("not.be.visible");
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, these tests don't take a long time to write and can be very powerful.&lt;br&gt;
Awesome! Now you know how to test a UI Designer page with Cypress! You can also check our &lt;a href="https://github.com/bonitasoft/bonita-web-pages/tree/master/uid-pages"&gt;web-pages project&lt;/a&gt; containing pages that we are currently developing.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
