INTRODUCTION
As part of getting my feet wet with Power Pages, I'm creating a simple site that does most of things I'd expect a site might need to do. The more I play with the platform, the more potential I see for creating sites that range in complexity from simple to sophisticated. This is a GREAT feature - make simple things simple to do and make difficult things possible.
In this post, I'm going a little deeper with a discussion of bypassing the platform's built-in integration with Dataverse to performing customizations that let you roll your own UI and talk directly with SQL Server... no Dataverse required!
ASSUMPTIONS
This article will be most helpful if you have some familiarity with the following concepts
- HTML
- used to create our custom UI
- CSS (just a little in-line styling)
- used to style the UI
- JavaScript
- used to manipulate the HTML DOM
- and call our Power Automate Flow Endpoints
- Power Automate Flows
- to get data from the UI and send it to SQL Server
- Email validation
- New user registration creation
- but you could use any API you like
- Power Pages
- we won't be explaining the basics of creating a site and adding Pages/Components
- Bootstrap (ugh... 3.x)
- used to style the pages
- used to implement a Modal message box
As shown in a previous post, we have a simple site that promotes a software product. On the Home page, there is an immediate call to action: Register. The Registration page
- Prompts for basic information
- Validates a non-duplicated email address
- using a Power Automate Api call
- Creates the new User in the database
- using a Power Automate Api call
- Adds the user
- Assigns appropriate roles
- Sends a confirmation email with Quick Start attachment
- Uses a Bootstrap Modal
- for Validation/Api messages
- HTML file
- defines the layout and elements on the page
- populated with HTML markup as you add elements (like text, images, etc) to the page
- CSS file
- defines the styling of the page and elements
- empty by default
- JavaScript file
- defines behaviors
- empty by default
It will look something like this. When you select a file, as shown below, that file will be loaded into the VS Code editor for you to make any changes you like
If you've done any web development, your eyes should be getting wide at this point. With access to these files, you can customize a site page to do lots of things that you couldn't otherwise accomplish.... like creating a custom Registration form that interacts with SQL Server through an API.... And that's exactly what we'll be showing next!
This gives us a perfect place to put our HTML for the Registration fields and submit button. After the custom HTML changes (shown below), it will look like this
label {
text-transform: uppercase;
font-weight: 600;
font-size: 80%;
}
.fldRequired {
border-left: 5px solid red;
}
.fldRecommended {
border-left: 5px solid yellow;
}
.fldValidated {
border-left: 5px solid green;
}
<div data-component-theme="portalThemeColor3" class="row sectionBlockLayout text-left" style="display: flex; flex-wrap: wrap; margin: 0px; min-height: auto; padding: 8px;"> <div class="container" style="display: flex; flex-wrap: wrap;"> <div class="col-md-6 columnBlockLayout" style="flex-grow: 1; min-width: 310px; word-break: break-word; display: flex; flex-direction: column; margin: 25px 0px; padding: 16px;"> <div class="row sectionBlockLayout" style="display: flex; flex-wrap: wrap; padding: 8px; margin: 0px; min-height: 15px;"></div> <div class="row sectionBlockLayout" style="display: flex; flex-wrap: wrap; padding: 8px; margin: 0px; min-height: 15px;"></div> <h3 style="text-align: left;">Registration</h3> <p style="text-align: left;">Want to see what iQueue can do for you? Take a few seconds to register and you'll get an email with login information and a Quick Start guide so that you can kick the tires. The "demo" site is fully functional and waiting for you - elevate your Process Management today!</p> </div> <div class="col-md-6 columnBlockLayout" style="flex-grow: 1; min-width: 310px; word-break: break-word; display: flex; flex-direction: column; margin: 25px 0px; padding: 16px;"><img src="/Registration/register.jpg" alt="" name="register.jpg" style="width: 100%; height: 295px; max-width: 100%; box-shadow: 0px 0px 6px rgb(0, 0, 0); border-radius: 5px;" /></div> </div> </div> <div class="row sectionBlockLayout text-left" style="display: flex; flex-wrap: wrap; margin: 0px; min-height: auto; padding: 8px; background-image: linear-gradient(0deg, rgba(161, 159, 157, 0), rgba(161, 159, 157, 0)); border: 0px solid rgb(161, 159, 157); border-radius: 0px;"> <div class="container" style="padding: 0px; display: flex; flex-wrap: wrap;">
<!--the customized form section--> <div class="col-md-12 columnBlockLayout" style="flex-grow: 1; display: flex; flex-direction: column; min-width: 310px; word-break: break-word; margin: 20px 0px; padding: 16px;"> <div class="mb-3" style="margin-bottom: 15px;"><label for="firstName">First Name</label><input type="text" id="firstName" class="form-control fldRequired" /></div> <div class="mb-3" style="margin-bottom: 15px;"><label for="lastName">Last Name</label><input type="text" id="lastName" class="form-control fldRequired" /></div> <div class="mb-3" style="margin-bottom: 15px;"><label for="email">Email</label><input type="text" id="email" class="form-control fldRequired" /></div> <div class="mb-3" style="margin-bottom: 15px;"><label for="company">Company</label><input type="text" id="company" class="form-control" /></div> <div class="mb-3"><button onclick="submitRegistration()" class="btn btn-primary">Submit</button></div> </div> <!--end customized form section-->
</div> </div>
<!--error modal--> <div class="modal fade" id="errorModal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="errorModalLabel">...</h4> </div> <div class="modal-body" id="errorModalBody"> ... </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal -->
- a Data Entry div
- we use this to gather the data from the visitor and then pass it along to the API endpoints
- a Bootstrap Modal div
- for messaging
console.log("initRegistrations started");
document.getElementById('email').addEventListener(
'change',
email_Change
);
document.querySelectorAll('.fldRequired').forEach(c => {
c.addEventListener('change', event => {
validateRequired(c);
});
});
setTimeout(() => {
document.querySelectorAll('.fldRequired').forEach(c => {
validateRequired(c);
});
}, 1000);
function email_Change(){
//some code here to see if that email is in play already
console.log("email_Change executed");
let payload = {};
payload.email = document.getElementById('email').value;
if (payload.email.length === 0){return;}
let url = "https://prod-84.westus.logic.azure.com:443/workflows/abc/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=123";
executeApiRequest(payload, url,
function(retVal){
if (retVal.length > 0){
document.getElementById('email').value = "";
validateRequired(document.getElementById("email"));
popModal("Duplicate Email", "The email you entered is already in use - please enter a different email address.");
}
},
function(error){},
true);
}
function executeApiRequest (parameters, url, successCallback, errorCallback, async) {
let uri = url;
let req = new XMLHttpRequest();
req.open("POST", uri, async);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("Cache-Control", "no-cache'");
if (async) {
req.onreadystatechange = function () {
if (this.readyState === 4 /* complete */) {
req.onreadystatechange = null;
if (this.status === 200) {
let data = JSON.parse(this.response, dateReviver);
successCallback(data);
}
else {
errorCallback(this.responseText);
}
}
};
parameters ? req.send(JSON.stringify(parameters)) : req.send();
}
else {
parameters ? req.send(JSON.stringify(parameters)) : req.send();
if (req.status === 200 || req.status === 204) {
if (req.status === 200) {
let data = JSON.parse(req.response, dateReviver);
successCallback(data);
}
}
else {
errorCallback(this.responseText);
}
}
}
function dateReviver (key, value) {
if (typeof value === 'string') {
let a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
}
}
return value;
}
function validateRequired(c) {
if (c.value.length > 0) {
c.style.borderLeft = "5px solid green";
}
else {
c.style.borderLeft = "5px solid red";
}
}
function submitRegistration() {
console.log("submit button clicked");
let reg = {};
reg.FirstName = document.getElementById("firstName").value;
reg.LastName = document.getElementById("lastName").value;
reg.Email = document.getElementById("email").value;
reg.Company = document.getElementById("company").value;
let updateOk = true;
document.querySelectorAll(".fldRequired").forEach(c => {
if (window.getComputedStyle(c).borderLeft.indexOf("red") >= 0 ||
window.getComputedStyle(c).borderLeft.indexOf("255") >= 0) {
updateOk = false;
}
});
if (!updateOk){
popModal("Validation", "Fields marked in RED are required")
return;
}
let url = "https://prod-176.westus.logic.azure.com:443/workflows/abc/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=123";
//validations passed... create the registration record
executeApiRequest(reg, url,
function(retVal){
popModal("Success", "Registration Succeeded!<br/><br/>Be sure to check for the Confimration email.<br/>Stand by for automatic navigation to Home page.");
setTimeout(function(){
window.location.href = "https://iqueue.powerappsportals.com/";
}, 10000);
},
function(error){},
true);
}
function popModal(title, body){
document.getElementById("errorModalLabel").innerHTML = title;
document.getElementById("errorModalBody").innerHTML = body;
$('#errorModal').modal('show');
}
- When the Registration page loads
- add an Event Listener to the email field so that we can fire the function to check for duplicates
- add an Event Listener to all required fields so that we can provide the visual cues
- do a validation to set the required field visual (the red left border)
- email_Change
- sends the email value entered by the user to the Email Validation Flow and processes the result
- executeApiRequest
- a reusable XmlHttpRequest function to call the API Flows
- dateReviver
- a help for executeApiRequest to generate friendly date formats
- validateRequired
- sets the left border for required fields
- submitRegistration
- gathers the Registration information and sends it to the Create Registraion Flow
- popModal
- populates and displays the notifications div
No comments:
Post a Comment