Check out the full project :
Contents hide
Overview :
This is the final part of the database-based config property project. If you haven’t checked out the first and second parts, we strongly recommend you to click the above link and read the previous articles for a better understanding and complete knowledge.
In this part, we will write flow services which will be invoked from the UI and update the existing index.dsp and the script.js files to communicate from the frontend.
Flow Services :
Insert New property :
- Create a flow service inside the services folder with the name ‘addNewProperty‘.
- input: Input variables of String type
- projectName
- action
- configKey
- configValue
- output: Output variables of String type
- result
- input: Input variables of String type
- Invoke TRY-CATCH step
- TRY :
- Invoke the BRANCH step and set the switch property on ‘action’.
- Within the switch statement, incorporate a SEQUENCE step and a MAP step. Assign the label ‘newProperty’ to the SEQUENCE step and map it to the $default case.
- Invoke ‘HGUtil.v1.adapter:batchConfigInsert‘ adapter service inside the SEQUENCE step which we have created in the first part of this article and follow the below mapping.
- projectName ->batchConfigInsertInput/inputs[0]/PROJECT_NAME
- configKey ->batchConfigInsertInput/inputs[0]/CONFIG_KEY
- configValue ->batchConfigInsertInput/inputs[0]/CONFIG_VALUE
- Invoke map step and hard code ‘result‘ variable with ‘Data has been inserted to Config Table‘.
- CATCH :
- Invoke ‘pub.flow:getLastError’ to capture the error.
- Invoke the BRANCH step and set the switch property on ‘lastError/error’.
- Invoke ‘pub.flow:throwExceptionForRetry’ inside BRANCH and set the label to ‘/ORA-\d+: unique constraint \([^)]+\) violated/’ and follow the below mapping.
- Hardcode ‘message’ variable with ‘New Property Entry Failed because of Unique constraint violation.’.
- Invoke ‘pub.flow:throwExceptionForRetry’ inside BRANCH and set the label to ‘$default’ and follow the below mapping.
- Hardcode ‘message’ variable with ‘Something went wrong.’.
- Insert the map step and drop all variables except the ‘result’ variable.
Update property :
- Create a flow service inside the services folder with the name ‘editProperty‘.
- input: Input variables of String type
- action
- configKey
- configValue
- output: Output variables of String type
- result
- input: Input variables of String type
- Invoke the BRANCH step and set the switch property on ‘action’.
- Within the switch statement, incorporate a SEQUENCE step and a MAP step. Assign the label ‘editConfig’ to the SEQUENCE step and map it to the $default case.
- Invoke ‘HGUtil.v1.adapter:updateConfigProperty’ adapter service inside SEQUENCE step which we have created in the first part of this article and follow the below mapping.
- configKey ->updateConfigPropertyInput/configKey
- configValue -> updateConfigPropertyInput/CONFIG_VALUE
- Invoke map step and hard code ‘result‘ variable with ‘Config value for Config Key =%configKey% has been updated.’ additionally check the variable substitution option.
- Insert the map step and drop all variables except the ‘result’ variable.
Delete property :
- Create a flow service inside the services folder with the name ‘deleteProperty‘.
- input: Input variables of String type
- action
- configKey
- output: Output variables of String type
- result
- input: Input variables of String type
- Invoke the BRANCH step and set the switch property on ‘action’.
- Within the switch statement, incorporate a SEQUENCE step and a MAP step. Assign the label ‘deleteConfig’ to the SEQUENCE step and map it to the $default case.
- Invoke ‘HGUtil.v1.adapter:deleteConfigProperty’ adapter service inside the SEQUENCE step which we have created in the first part of this article and follow the below mapping.
- configKey ->deleteConfigPropertyInput/ConfigKey
- Invoke the map step and hard code ‘result‘ variable with ‘Property entry has been deleted for Config Key =%configKey%.’ additionally check the variable substitution option.
- Insert the map step and drop all variables except the ‘result’ variable.
Implement View Property functionality:
- We’ve developed an adapter service responsible for fetching a list of configuration properties from the database. Our next step involves invoking this adapter service within our index.dsp page and populate the table with the retrieved data.
- Update the table HTML script in the index.dsp page with the below code so that we will be able to fetch and load it into the table.
<table id="myTable"> <thead> <tr> <th>PROJECT_NAME</th> <th>CONFIG_KEY</th> <th>CONFIG_VALUE</th> <th colspan="2">Action</th> </tr> </thead> %invoke HGUtil.v1.adapter:selectConfigProperty% <tbody> %loop selectConfigPropertyOutput/results% <tr> <td contenteditable="false">%value PROJECT_NAME%</td> <td contenteditable="false">%value CONFIG_KEY%</td> <td contenteditable="true">%value CONFIG_VALUE%</td> <td><button class="action-btn" onclick="editRow(this)">Edit</button></td> <td><button class="action-btn" onclick="deleteRow(this)">Delete</button></td> </tr> %endloop% </tbody> %onerror% <script> alert("%value errorMessage%"); </script> %endinvoke% </table>
- Once the provided code is integrated into the index.dsp page, you’ll be able to visualize the configuration properties retrieved from the database as a table.
Implement Insert new Property functionality:
- Create a new insert.dsp page inside the pub folder and insert the below code into it to send the request and handle the response.
<!DOCTYPE html> <html> <head> <title>Config Manager</title> </head> <body> <!--Add property--> %invoke HGUtil.v1.services:addNewProperty% <script> if('%value result%'!='') { alert('%value result%'); document.location.replace('index.dsp'); } </script> %onerror% <script> alert('%value errorMessage%'); document.location.replace('index.dsp'); </script> %endinvoke% </body> <!-- Include the external JavaScript file --> </html>
- Now we need to write the button click event for the ‘Add Property’, which will collect the property data and pass it to the insert.dsp page and the same will be inserted into the database.
- Add the below code in the script.js file to insert the new property entry to the database.
function addNewRow() { // Get the input values by their names const projectName = document.querySelector('input[name="projectName"]').value; const configKey = document.querySelector('input[name="configKey"]').value; const configValue = document.querySelector('input[name="configValue"]').value; var url="insert.dsp?action=newProperty&"+"projectName="+projectName+"&configKey="+configKey+"&configValue="+configValue; document.location.replace(url); }
- Once the above code is implemented we will be able to insert the properties into the config table.
Implement Edit Property functionality:
- Create a new edit.dsp page inside the pub folder and insert the below code into it to send the edit request and handle the response.
<!DOCTYPE html> <html> <head> <title>Config Manager</title> </head> <body> <!--Edit property--> %invoke HGUtil.v1.services:editProperty% <script> if('%value result%'!='') { alert('%value result%'); document.location.replace('index.dsp'); } </script> %onerror% <script> alert('%value errorMessage%'); document.location.replace('index.dsp'); </script> %endinvoke% </body> <!-- Include the external JavaScript file --> </html>
- Update the “Edit” button’s click event to generate a new “Save” button. When the “Save” button is clicked, the edited data will be transmitted to the backend flow service, which will update the table with the changes.
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 // Add an event listener to the "Save" button button.addEventListener('click', function () { saveRow(row); }); } else { configValueCell.contentEditable = false; button.textContent = 'Edit'; row.classList.remove('editing'); // Remove the editing class // Remove the event listener from the "Save" button button.removeEventListener('click', saveRow); } }
- Add the following function to the script.js file in order to transmit the data to the backend flow service for update.
function saveRow(editObj){ const row = editObj.closest('tr'); if (row) { // Assuming data is in the first and second <td> elements of the row const configKey = row.querySelector('td:nth-child(2)').textContent; const configValue = row.querySelector('td:nth-child(3)').textContent; // Do something with the row data // Display a confirmation dialog const confirmation = window.confirm('Are you sure you want to save the edited data?'); if (confirmation) { // User confirmed, proceed with the save action var url = "edit.dsp?action=editConfig&" + "configKey=" + configKey + "&configValue=" + configValue; document.location.replace(url); } else { // User canceled the action, do nothing or provide feedback } } }
- Once these codes are updated we will be able to edit the existing property entries.
Implement Delete Property functionality:
- Create a new delete.dsp page inside the pub folder and insert the below code into it to send the delete request and response.
<!DOCTYPE html> <html> <head> <title>Config Manager</title> </head> <body> <!--Edit property--> %invoke HGUtil.v1.services:deleteProperty% <script> if('%value result%'!='') { alert('%value result%'); document.location.replace('index.dsp'); } </script> %onerror% <script> alert('%value errorMessage%'); document.location.replace('index.dsp'); </script> %endinvoke% </body> <!-- Include the external JavaScript file --> </html>
- Next, we must create a function to manage the “Delete” button’s click event so Insert the following code into the script.js file.
//Delete event function deleteRow(deleteObj) { // Find the closest <tr> ancestor of the clicked button const row = deleteObj.closest('tr'); if (row) { // Assuming data is in the first and second <td> elements of the row const configKey = row.querySelector('td:nth-child(2)').textContent; const confirmation = window.confirm('Are you sure you want to delety Config property with ConfigKey='+configKey+' the edited data?'); if (confirmation) { // User confirmed, proceed with the save action var url = "delete.dsp?action=deleteConfig&" + "configKey=" + configKey; document.location.replace(url); } else { // User canceled the action, do nothing or provide feedback } } }
Conclusion:
- At this stage, we have completed the coding process. Assuming we have executed all the steps accurately, all the functionalities should be operational, and the index.dsp and script.js files should resemble the examples provided below.
- index.dsp file :
<!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> %invoke HGUtil.v1.adapter:selectConfigProperty% <tbody> %loop selectConfigPropertyOutput/results% <tr> <td contenteditable="false">%value PROJECT_NAME%</td> <td contenteditable="false">%value CONFIG_KEY%</td> <td contenteditable="true">%value CONFIG_VALUE%</td> <td><button class="action-btn" onclick="editRow(this)">Edit</button></td> <td><button class="action-btn" onclick="deleteRow(this)">Delete</button></td> </tr> %endloop% </tbody> %onerror% <script> alert("%value errorMessage%"); </script> %endinvoke% </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" name="projectName"> <input id="newConfigKey" class="modal-input" type="text" placeholder="CONFIG_KEY" name="configKey"> <input id="newConfigValue" class="modal-input" type="text" placeholder="CONFIG_VALUE" name="configValue"> <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 :
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 // Add an event listener to the "Save" button button.addEventListener('click', function () { saveRow(row); }); } else { configValueCell.contentEditable = false; button.textContent = 'Edit'; row.classList.remove('editing'); // Remove the editing class // Remove the event listener from the "Save" button button.removeEventListener('click', saveRow); } } // 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; // Set display to 'table-row' if the row should be shown, or an empty string to clear any previous styling. row.style.display = shouldHide ? 'none' : ''; }); }); 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 = 5; // 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(); //Delete event function deleteRow(deleteObj) { // Find the closest <tr> ancestor of the clicked button const row = deleteObj.closest('tr'); if (row) { // Assuming data is in the first and second <td> elements of the row const configKey = row.querySelector('td:nth-child(2)').textContent; const confirmation = window.confirm('Are you sure you want to delety Config property with ConfigKey='+configKey+' the edited data?'); if (confirmation) { // User confirmed, proceed with the save action var url = "delete.dsp?action=deleteConfig&" + "configKey=" + configKey; document.location.replace(url); } else { // User canceled the action, do nothing or provide feedback } } } function addNewRow() { // Get the input values by their names const projectName = document.querySelector('input[name="projectName"]').value; const configKey = document.querySelector('input[name="configKey"]').value; const configValue = document.querySelector('input[name="configValue"]').value; var url="insert.dsp?action=newProperty&"+"projectName="+projectName+"&configKey="+configKey+"&configValue="+configValue; document.location.replace(url); } function saveRow(editObj){ const row = editObj.closest('tr'); if (row) { // Assuming data is in the first and second <td> elements of the row const configKey = row.querySelector('td:nth-child(2)').textContent; const configValue = row.querySelector('td:nth-child(3)').textContent; // Do something with the row data // Display a confirmation dialog const confirmation = window.confirm('Are you sure you want to save the edited data?'); if (confirmation) { // User confirmed, proceed with the save action var url = "edit.dsp?action=editConfig&" + "configKey=" + configKey + "&configValue=" + configValue; document.location.replace(url); } else { // User canceled the action, do nothing or provide feedback } } }
Source Code:
Click the below Download button to download the source code