Check out the full project :
Overview :
This is the second part of the database-based config property project. If you haven’t checked out the first part, we strongly recommend you click on the above link and read the previous article for a better understanding and complete knowledge.
In this part, we will explore and build the user interface (UI) for inserting, updating, and managing key-value pairs for properties.
Prerequisite :
- Basic knowledge of the DSP page(Click here to check out our article on the DSP page).
- Basic knowledge of HTML CSS and Javascript.
Create a DSP page :
Note: We will continue with the same package we created in the part 1 article.
- Create index.html, style.css, and script.js in the following location: [C:\SoftwareAG\IntegrationServer\instances\default\packages\HGUtil\pub]
Note: This location may differ based on your integration server installation directory.
- Insert the following code into the index.dsp page so that the external script.js and style.css can be used.
<!DOCTYPE html> <html> <head> <title>Config Manager</title> <!-- Include the external CSS file --> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <!-- Your HTML content goes here --> </body> <!-- Include the external JavaScript file --> <script src="script.js"></script> </html>
Add DSP page to IS admin page :
- In the Designer, navigate to the ‘ui’ folder within the ‘HGUtil’ package, and create a new flow service named ‘setSolutionParam’.
- Insert a map step to create the following variables and assign them with the specified hardcoded values.
- target: index.dsp
- name: Config Framework
- text: Config Framework
- url: ../HGUtil/index.dsp
- Create one more flow service with the name ‘createSolution’ in the ‘ui’ folder.
- In the ‘createSolution’ flow service, invoke ‘wm.server.ui:addSolution’ and hardcode the ‘callback’ parameter with the full path of the ‘setSolutionParam’ service, for example, ‘HGUtil.v1.ui:setSolutionParam’.
- Now run the ‘createSolution’ service and log in to the IS admin page, you will notice ‘Config Framework’ option has been added under the solution tab, and if you click on that it will open the index.dsp page.
- Once you click on the ‘Config Framework’ option, it will open one empty page as we have not designed it yet.
Design UI :
Create Table :
- Please insert the following HTML code into the index.dsp page to create the table for viewing the configuration properties.
<div class="container"> <div> <h1>Config Property Update</h1> <table> <thead> <tr> <th>PROJECT_NAME</th> <th>CONFIG_KEY</th> <th>CONFIG_VALUE</th> <th colspan="2">Action</th> </tr> </thead> <tbody> <tr> <td contenteditable="false">Project 1</td> <td contenteditable="false">unique_key_1</td> <td contenteditable="true">Value 1</td> <td><button class="action-btn" onclick="editRow(this)">Edit</button></td> <td><button class="action-btn" onclick="deleteRow()">Delete</button></td> </tr> <tr> <td contenteditable="false">Project 2</td> <td contenteditable="false">unique_key_2</td> <td contenteditable="true">Value 2</td> <td><button class="action-btn" onclick="editRow(this)">Edit</button></td> <td><button class="action-btn" onclick="deleteRow()">Delete</button></td> </tr> <!-- Add more rows as needed --> </tbody> </table> </div>
- Add the following code to ‘script.js’ to toggle between edit and save and change the color when an item is selected for editing.
function editRow(button) { const row = button.parentNode.parentNode; const configValueCell = row.querySelector('td:nth-child(3)'); if (button.textContent === 'Edit') { configValueCell.contentEditable = true; button.textContent = 'Save'; row.classList.add('editing'); // Add the editing class } else { configValueCell.contentEditable = false; button.textContent = 'Edit'; row.classList.remove('editing'); // Remove the editing class } }
- Now, please insert the following code into your style.css to improve the appearance of the user interface (UI).
body { font-family: Arial, sans-serif; background-color: #f0f0f0; margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .container { width: 80%; max-width: 1000px; background-color: #fff; box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1); border-radius: 10px; padding: 20px; } h1 { text-align: center; color: #2c3e50; margin-bottom: 20px; font-size: 30px; } table { border-collapse: collapse; width: 100%; border: 1px solid #dcdde1; margin-top: 20px; background-color: #ffffff; } th, td { padding: 12px; text-align: left; border-bottom: 1px solid #dcdde1; color: #333; } th { background-color: #f5f6fa; font-size: 16px; font-weight: 600; } tr:hover { background-color: #f8f9fa; } /* ... your existing styles ... */ .editing { background-color: #f7dc6f; /* Yellow color */ } .editing td:nth-child(3) { background-color: #ff6b6b; /* Red color */ }
- Now, add the following code after the
<h1>
tag and before the table starts to insert a search box for searching config properties based on a key, and a row count to display how many data entries are available in the table.
<input type="text" id="searchInput" class="search-bar" placeholder="Search by CONFIG_KEY"> <div id="rowCount">Row Count: 0</div>
- Now we have created the search box but it is not decorated yet, it needs decoration. Please add the following code to the style.css file.
.search-bar { margin-bottom: 20px; padding: 8px; border: 1px solid #dcdde1; border-radius: 4px; width: 100%; font-size: 14px; }
- Now, we need to count the rows in the table. Add the following JavaScript code to
script.js
so that the rows can be calculated and populated in the row count section.
// Select the tbody element var tbody = document.querySelector('table tbody'); // Count the number of rows in the tbody var rowCount = tbody.children.length; document.getElementById('rowCount').innerHTML = 'Row Count: '+rowCount;
- Add the following code to the ‘script.js’ file to implement a table row filter based on the key entered by the end user.
document.getElementById('searchInput').addEventListener('input', function() { const filter = this.value.toLowerCase(); const rows = document.querySelectorAll('tbody tr'); rows.forEach(row => { const configKeyCell = row.querySelector('td:nth-child(2)'); const shouldHide = configKeyCell.textContent.toLowerCase().indexOf(filter) === -1; row.style.display = shouldHide ? 'none' : 'table-row'; }); });
Add Property :
- Update the ‘index.html’ page with the following code to create a popup that allows users to insert new property entries.
- Add the below code after the table and inside the div tag to create a button using which the popup can be opened to insert property data.
<button class="add-row-btn" onclick="openModal()">Add Property</button>
- Insert the below code to ‘style.css’ to decorate the button.
/* Add CSS rules to style the button */ .add-row-btn { background-color: #0074d9; /* Change the background color */ color: #fff; /* Change the text color */ border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; margin-top:10px; } /* Add a hover effect when the mouse is over the button */ .add-row-btn:hover { background-color: #0056b3; /* Change the background color on hover */ }
- Insert the following HTML code after the
<div>
tag to create a new popup, which will be used for inserting new properties.
<div id="myModal" class="modal"> <div class="modal-content"> <h2>Add New Row</h2> <input id="newProjectName" class="modal-input" type="text" placeholder="PROJECT_NAME"> <input id="newConfigKey" class="modal-input" type="text" placeholder="CONFIG_KEY"> <input id="newConfigValue" class="modal-input" type="text" placeholder="CONFIG_VALUE"> <button class="action-btn" onclick="addNewRow()">Add</button> <button class="action-btn" onclick="closeModal()">Cancel</button> </div> </div>
- Please insert the following code into the ‘style.css’ file to style or decorate the popup form.
.modal { display: none; position: fixed; z-index: 1; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); } .modal-content { background-color: #fff; margin: auto; padding: 20px; border-radius: 5px; width: 50%; box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.5); } .modal-input { display: block; margin-bottom: 10px; width: 100%; padding: 8px; border: 1px solid #dcdde1; border-radius: 4px; font-size: 14px; }
- Insert the following js script into the ‘script.js’ file to handle the popup.
function openModal() { const modal = document.getElementById('myModal'); modal.style.display = 'block'; } function closeModal() { const modal = document.getElementById('myModal'); modal.style.display = 'none'; }
- Insert the below code into the ‘style.css’ to decorate buttons.
.update-btn, .add-row-btn, .action-btn { background-color: #3498db; color: #fff; border: none; padding: 6px 10px; border-radius: 4px; cursor: pointer; transition: background-color 0.3s ease; font-size: 14px; margin-right: 5px; }
- Add the below code after the table and inside the div tag to create a button using which the popup can be opened to insert property data.
- We will write javascript code in the next part to load the property into the database.
Table pagination :
- As this will be a common UI for property management, with a large number of properties, we will be adding pagination for easy navigation between pages
- Since we’ll be implementing pagination for the table, add the following attribute to the existing table as shown below.
<table id="myTable">
- After the table script in the index.dsp file, insert the following code to enable page navigation and display the number of pages.
<div class="pagination"> <a id="prev">Previous</a> <span id="page-info"></span> <a id="next">Next</a> </div>
- Add the following CSS to the ‘style.css’ file to style the pagination buttons.
/* Add CSS styles for pagination */ .pagination { display: inline-block; } .pagination a { color: black; display: inline-block; /* Display as block-level element */ background-color: #0074d9; /* Button background color */ color: #fff; /* Button text color */ padding: 8px 16px; text-decoration: none; cursor: pointer; border: none; border-radius: 4px; margin-right: 5px; /* Add margin between buttons */ } /* Style the anchor when hovered */ .pagination a:hover { background-color: #0056b3; /* Button background color on hover */ }
- Insert the following code into the script.js file to enable pagination functionality.
// Table rows and pagination variables var table = document.getElementById("myTable"); var rows = table.tBodies[0].rows; var itemsPerPage = 2; // Change this to adjust the number of rows per page var currentPage = 1; // Function to display the desired page of items function displayPage(page) { for (var i = 0; i < rows.length; i++) { if (i >= (page - 1) * itemsPerPage && i < page * itemsPerPage) { rows[i].style.display = "table-row"; } else { rows[i].style.display = "none"; } } } // Function to generate the pagination links function setupPagination() { var pageCount = Math.ceil(rows.length / itemsPerPage); var pagination = document.querySelector(".pagination"); var pageInfo = document.getElementById("page-info"); var prevButton = document.getElementById("prev"); var nextButton = document.getElementById("next"); prevButton.addEventListener("click", function () { if (currentPage > 1) { currentPage--; displayPage(currentPage); updatePageInfo(); } }); nextButton.addEventListener("click", function () { if (currentPage < pageCount) { currentPage++; displayPage(currentPage); updatePageInfo(); } }); function updatePageInfo() { pageInfo.textContent = "Page " + currentPage + " of " + pageCount; } displayPage(currentPage); updatePageInfo(); } // Initialize pagination setupPagination();
Final Code :
If you’ve followed the instructions correctly, your index.dsp, script.js, and style.css should now look like this.
- index.dsp file content :
<!DOCTYPE html> <html> <head> <title>Config Manager</title> <!-- Include the external CSS file --> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <!-- Your HTML content goes here --> <div class="container"> <div> <h1>Config Property Update</h1> <input type="text" id="searchInput" class="search-bar" placeholder="Search by CONFIG_KEY"> <div id="rowCount">Row Count: 0</div> <table id="myTable"> <thead> <tr> <th>PROJECT_NAME</th> <th>CONFIG_KEY</th> <th>CONFIG_VALUE</th> <th colspan="2">Action</th> </tr> </thead> <tbody> <tr> <td contenteditable="false">Project 1</td> <td contenteditable="false">unique_key_1</td> <td contenteditable="true">Value 1</td> <td><button class="action-btn" onclick="editRow(this)">Edit</button></td> <td><button class="action-btn" onclick="deleteRow()">Delete</button></td> </tr> <tr> <td contenteditable="false">Project 2</td> <td contenteditable="false">unique_key_2</td> <td contenteditable="true">Value 2</td> <td><button class="action-btn" onclick="editRow(this)">Edit</button></td> <td><button class="action-btn" onclick="deleteRow()">Delete</button></td> </tr> <!-- Add more rows as needed --> </tbody> </table> <button class="add-row-btn" onclick="openModal()">Add Property</button> <div class="pagination"> <a id="prev">Previous</a> <span id="page-info"></span> <a id="next">Next</a> </div> </div> <div id="myModal" class="modal"> <div class="modal-content"> <h2>Add New Row</h2> <input id="newProjectName" class="modal-input" type="text" placeholder="PROJECT_NAME"> <input id="newConfigKey" class="modal-input" type="text" placeholder="CONFIG_KEY"> <input id="newConfigValue" class="modal-input" type="text" placeholder="CONFIG_VALUE"> <button class="action-btn" onclick="addNewRow()">Add</button> <button class="action-btn" onclick="closeModal()">Cancel</button> </div> </div> </body> <!-- Include the external JavaScript file --> <script src="script.js"></script> </html>
- script.js file content :
function editRow(button) { const row = button.parentNode.parentNode; const configValueCell = row.querySelector('td:nth-child(3)'); if (button.textContent === 'Edit') { configValueCell.contentEditable = true; button.textContent = 'Save'; row.classList.add('editing'); // Add the editing class } else { configValueCell.contentEditable = false; button.textContent = 'Edit'; row.classList.remove('editing'); // Remove the editing class } } // Select the tbody element var tbody = document.querySelector('table tbody'); // Count the number of rows in the tbody var rowCount = tbody.children.length; document.getElementById('rowCount').innerHTML = 'Row Count: '+rowCount; document.getElementById('searchInput').addEventListener('input', function() { const filter = this.value.toLowerCase(); const rows = document.querySelectorAll('tbody tr'); rows.forEach(row => { const configKeyCell = row.querySelector('td:nth-child(2)'); const shouldHide = configKeyCell.textContent.toLowerCase().indexOf(filter) === -1; row.style.display = shouldHide ? 'none' : 'table-row'; }); }); function openModal() { const modal = document.getElementById('myModal'); modal.style.display = 'block'; } function closeModal() { const modal = document.getElementById('myModal'); modal.style.display = 'none'; } // Table rows and pagination variables var table = document.getElementById("myTable"); var rows = table.tBodies[0].rows; var itemsPerPage = 2; // Change this to adjust the number of rows per page var currentPage = 1; // Function to display the desired page of items function displayPage(page) { for (var i = 0; i < rows.length; i++) { if (i >= (page - 1) * itemsPerPage && i < page * itemsPerPage) { rows[i].style.display = "table-row"; } else { rows[i].style.display = "none"; } } } // Function to generate the pagination links function setupPagination() { var pageCount = Math.ceil(rows.length / itemsPerPage); var pagination = document.querySelector(".pagination"); var pageInfo = document.getElementById("page-info"); var prevButton = document.getElementById("prev"); var nextButton = document.getElementById("next"); prevButton.addEventListener("click", function () { if (currentPage > 1) { currentPage--; displayPage(currentPage); updatePageInfo(); } }); nextButton.addEventListener("click", function () { if (currentPage < pageCount) { currentPage++; displayPage(currentPage); updatePageInfo(); } }); function updatePageInfo() { pageInfo.textContent = "Page " + currentPage + " of " + pageCount; } displayPage(currentPage); updatePageInfo(); } // Initialize pagination setupPagination();
- style.css file content :
body { font-family: Arial, sans-serif; background-color: #f0f0f0; margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .container { width: 80%; max-width: 1000px; background-color: #fff; box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1); border-radius: 10px; padding: 20px; } h1 { text-align: center; color: #2c3e50; margin-bottom: 20px; font-size: 30px; } table { border-collapse: collapse; width: 100%; border: 1px solid #dcdde1; margin-top: 20px; background-color: #ffffff; } th, td { padding: 12px; text-align: left; border-bottom: 1px solid #dcdde1; color: #333; } th { background-color: #f5f6fa; font-size: 16px; font-weight: 600; } tr:hover { background-color: #f8f9fa; } /* ... your existing styles ... */ .editing { background-color: #f7dc6f; /* Yellow color */ } .editing td:nth-child(3) { background-color: #ff6b6b; /* Red color */ } .search-bar { margin-bottom: 20px; padding: 8px; border: 1px solid #dcdde1; border-radius: 4px; width: 100%; font-size: 14px; } /* Add CSS rules to style the button */ .add-row-btn { background-color: #0074d9; /* Change the background color */ color: #fff; /* Change the text color */ border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; margin-top:10px; } /* Add a hover effect when the mouse is over the button */ .add-row-btn:hover { background-color: #0056b3; /* Change the background color on hover */ } .modal { display: none; position: fixed; z-index: 1; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); } .modal-content { background-color: #fff; margin: auto; padding: 20px; border-radius: 5px; width: 50%; box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.5); } .modal-input { display: block; margin-bottom: 10px; width: 100%; padding: 8px; border: 1px solid #dcdde1; border-radius: 4px; font-size: 14px; } .update-btn, .add-row-btn, .action-btn { background-color: #3498db; color: #fff; border: none; padding: 6px 10px; border-radius: 4px; cursor: pointer; transition: background-color 0.3s ease; font-size: 14px; margin-right: 5px; } /* Add CSS styles for pagination */ .pagination { display: inline-block; } .pagination a { color: black; display: inline-block; /* Display as block-level element */ background-color: #0074d9; /* Button background color */ color: #fff; /* Button text color */ padding: 8px 16px; text-decoration: none; cursor: pointer; border: none; border-radius: 4px; margin-right: 5px; /* Add margin between buttons */ } /* Style the anchor when hovered */ .pagination a:hover { background-color: #0056b3; /* Button background color on hover */ } /* ... your existing styles ... */ .editing { background-color: #f7dc6f; /* Yellow color */ } .editing td:nth-child(3) { background-color: #ff6b6b; /* Red color */ }
Conclusion :
In this article, we have covered the step-by-step procedure to create the User interface for our DB-based config property management project, in the next part of this series we will deep dive into the DSP(Dynamic Server Pages) and write the DSP and flow service to make our UI fully functional.
I strongly recommend that you visit the webMethods DSP page documentation portal to understand how DSP pages work. You can click here to access the documentation. Additionally, you can check out our article on basic DSP page projects by clicking here.