Connecting HTML Web Resource and Forms in Dynamics 365 Model-Driven Apps with JavaScript (Implementing Dynamic Percentage Circles)
MERP Systems, Inc.
Transform your applications experience using Microsoft Azure Open AI, Copilot, Dynamics 365, SharePoint, and Power Apps
- Authored by Bincy Roy, Junior Software Applications Developer, MERP Systems
In the realm of Dynamics 365, customization often extends beyond the built-in features. A challenge arose on how to visually represent dynamic percentages for various skills on a Dynamics 365 form. This blog delves into the HTML and JavaScript work that led to the creation of dynamic percentage circles, providing an engaging and informative solution.?
?
1) Create Web Resources in PowerApps?
?
a. Form_onload.js?
?
Create a new JavaScript web resource named "Form_onload.js" in PowerApps. This script will handle the form onload event and set the client API context.?
function form_onload(executionContext) {?
? ? debugger;?
? ? var formContext = executionContext.getFormContext();?
? ? var wrControl = formContext.getControl("WebResource_name");??
// Replace "WebResource_name" with the actual name of your HTML web resource?
?
? ? if (wrControl) {?
? ? ? ? wrControl.getContentWindow().then(?
? ? ? ? ? ? function (contentWindow) {?
? ? ? ? ? ? ? ? contentWindow.setClientApiContext(Xrm, formContext);?
? ? ? ? ? ? }?
? ? ? ? )?
? ? }?
}?
?
b. Diagram.html?
?
Create a new HTML web resource named "Diagram.html" in PowerApps. This file will contain the HTML code, CSS styling, and JavaScript logic for dynamic percentage circles.?
?
The HTML document is structured to accommodate dynamic percentage circles for different skills. Counters, input fields, and canvases are used to create an interactive and visually appealing representation of skill levels. The design incorporates responsive elements for an optimal user experience.?
?
<!DOCTYPE html>?
<html lang="en">?
<head>?
? <!-- Set the character set and viewport for responsive design -->?
? <meta charset="UTF-8">?
? <meta name="viewport" content="width=device-width, initial-scale=1.0">?
? <!-- Set the title of the document -->?
? <title>Combined Code</title>?
</head>?
<body>?
? <!-- Main container with a class of 'wrapper' -->?
? <div class="wrapper">?
? ? <!-- Row containing both existing and new counters, with additional styling -->?
? ? <div class="row pt-5 pb-5" id="countersRow">?
? ? ? <!-- Existing counters -->?
? ? ? <div class="col-6 col-sm-3">?
? ? ? ? <!-- Counter for Technical Skills with a unique ID and color attribute -->?
? ? ? ? <div class="counter" id="skillsCounter" data-cp-color="#53405e"></div>?
? ? ? ? <!-- Heading for Technical Skills -->?
? ? ? ? <h4>Technical Skills</h4>?
? ? ? ? <!-- Input for entering a numerical value related to Technical Skills -->?
? ? ? ? <input type="number" id="TechnicalSkills" class="percentage-input" placeholder="0-100"/>?
? ? ? </div>?
? ? ? <div class="col-6 col-sm-3">?
? ? ? ? <!-- Counter for Problem Solving with a unique ID and color attribute -->?
? ? ? ? <div class="counter" id="contentCounter" data-cp-color="#e0bd93"></div>?
? ? ? ? <!-- Heading for Problem Solving -->?
? ? ? ? <h4>Problem Solving</h4>?
? ? ? ? <!-- Input for entering a numerical value related to Problem Solving -->?
? ? ? ? <input type="number" id="ProblemSolving" class="percentage-input" placeholder="0-100" ?/>?
? ? ? </div>?
? ? ? <div class="col-6 col-sm-3">?
? ? ? ? <!-- Counter for Communication Skills with a unique ID and color attribute -->?
? ? ? ? <div class="counter" id="websitesCounter" data-cp-color="#FF675B"></div>?
? ? ? ? <!-- Heading for Communication Skills -->?
? ? ? ? <h4>Communication Skills</h4>?
? ? ? ? <!-- Input for entering a numerical value related to Communication Skills -->?
? ? ? ? <input type="number" id="CommunicationSkills" class="percentage-input" placeholder="0-100"/>?
? ? ? </div>?
? ? ? <div class="col-6 col-sm-3">?
? ? ? ? <!-- Counter for Analytical Thinking with a unique ID and color attribute -->?
? ? ? ? <div class="counter" id="employeesCounter" data-cp-color="#529f82"></div>?
? ? ? ? <!-- Heading for Analytical Thinking -->?
? ? ? ? <h4>Analytical Thinking</h4>?
? ? ? ? <!-- Input for entering a numerical value related to Analytical Thinking -->?
? ? ? ? <input type="number" id="AnalyticalThinking" class="percentage-input" placeholder="0-100" />?
? ? ? </div>?
? ? ? <!-- New counters -->?
? ? ? <div class="col-6 col-sm-3">?
? ? ? ? <!-- Counter for Coding Proficiency with a unique ID and color attribute -->?
? ? ? ? <div class="counter" id="domainCounter" data-cp-color="#a23658"></div>?
? ? ? ? <!-- Heading for Coding Proficiency -->?
? ? ? ? <h4>Coding Proficiency</h4>?
? ? ? ? <!-- Input for entering a numerical value related to Coding Proficiency -->?
? ? ? ? <input type="number" id="CodingProficiency" class="percentage-input" placeholder="0-100" />?
? ? ? </div>?
? ? ? <div class="col-6 col-sm-3">?
? ? ? ? <!-- Counter for Domain Knowledge with a unique ID and color attribute -->?
? ? ? ? <div class="counter" id="codingCounter" data-cp-color="#1e656d"></div>?
? ? ? ? <!-- Heading for Domain Knowledge -->?
? ? ? ? <h4>Domain Knowledge</h4>?
? ? ? ? <!-- Input for entering a numerical value related to Domain Knowledge -->?
? ? ? ? <input type="number" id="DomainKnowledge" class="percentage-input" placeholder="0-100"/>?
? ? ? </div>?
? ? </div>?
? </div>?
</body>?
</html>?
?
?
CSS Styling?
?
CSS styling plays a crucial role in crafting visual aesthetics of dynamic percentage circles. The counters are designed with a circular shape, each representing a specific skill. Responsive design considerations ensure that the layout adapts to different screen sizes, offering a seamless experience.?
<style>?
? ? .counter {?
? ? ? display: inline-flex;?
? ? ? cursor: pointer;?
? ? ? width: 100px; /* Adjusted width */?
? ? ? height: 100px; /* Adjusted height */?
? ? ? max-width: 100%;?
? ? ? position: relative;?
? ? ? justify-content: center;?
? ? ? align-items: center;?
? ? ? font-size: calc(1em + 1vmin);?
? ? ? transition: height .2s ease-in-out;?
? ? ? background: #fff;?
? ? ? border-radius: 50%;?
? ? ? box-shadow: 0px 1px 10px 2px rgba(0, 0, 0, 0.2);?
? ? ? margin: 1em 0;?
? ? }?
? ? h4 {?
? ? ? ? margin-top: 0px;?
? ? }?
? ? .col-6.col-sm-3 {?
? ? margin: 46px;?
}?
? ? .percentage {?
? ? ? position: absolute;?
? ? ? text-align: center;?
? ? ? top: 50%;?
? ? ? left: 0;?
? ? ? right: 0;?
? ? ? vertical-align: middle;?
? ? ? transform: translate3d(0, -50%, 0);?
? ? }?
? ? .wrapper {?
? ? ? ? display: flex;?
? ? }?
? ? canvas {?
? ? ? position: absolute;?
? ? ? top: 0;?
? ? ? left: 0;?
? ? }?
? ? div#countersRow {?
? ? display: flex;?
}?
? ? input {?
? ? ? width: 100px; /* Adjusted input width */?
? ? }?
? ? body {?
? ? ? font-family: 'Open Sans', sans-serif;?
? ? ? text-align: center;?
? ? }?
? ? #TechnicalSkills,?
? ? #ProblemSolving,?
? ? #CommunicationSkills,?
? ? #AnalyticalThinking,?
? ? #CodingProficiency,?
? ? #DomainKnowledge{?
? ? display: none;?
}?
</style>?
?
JavaScript Logic?
?
The heart of the solution lies in the JavaScript logic. The circleProgress function initializes and animates circular progress based on percentage values. Input fields dynamically update the circles, providing real-time feedback. The logic includes a smooth animation function for an enhanced user experience.?
?
<script>?
? ? document.addEventListener("DOMContentLoaded", function () {?
?
? ? ? var circleProgress = (function (selector) {?
? ? ? ? var wrapper = document.querySelectorAll(selector);?
? ? ? ? Array.prototype.forEach.call (wrapper, function (wrapper, i) {?
? ? ? ? ? var wrapperWidth,?
? ? ? ? ? ? wrapperHeight,?
? ? ? ? ? ? percent,?
? ? ? ? ? ? innerHTML,?
? ? ? ? ? ? context,?
? ? ? ? ? ? lineWidth,?
? ? ? ? ? ? centerX,?
? ? ? ? ? ? centerY,?
? ? ? ? ? ? radius,?
? ? ? ? ? ? newPercent,?
? ? ? ? ? ? speed,?
? ? ? ? ? ? from,?
? ? ? ? ? ? to,?
? ? ? ? ? ? duration,?
? ? ? ? ? ? start,?
? ? ? ? ? ? strokeStyle,?
? ? ? ? ? ? text;?
领英推荐
?
? ? ? ? ? var getValues = function () {?
? ? ? ? ? ? wrapperWidth = parseInt(window.getComputedStyle(wrapper).width);?
? ? ? ? ? ? wrapperHeight = wrapperWidth;?
? ? ? ? ? ? percent = wrapper.getAttribute('data-cp-percentage');?
? ? ? ? ? ? innerHTML = '<span class="percentage"><strong>' + percent + '</strong> %</span>?
?? ??? ??? ?<canvas class="circleProgressCanvas" width="' +??
(wrapperWidth 2) + '" height="' + wrapperHeight 2 + '">?
?? ??? ??? ?</canvas>';?
? ? ? ? ? ? wrapper.innerHTML = innerHTML;?
? ? ? ? ? ? text = wrapper.querySelector(".percentage");?
? ? ? ? ? ? canvas = wrapper.querySelector(".circleProgressCanvas");?
? ? ? ? ? ? wrapper.style .height = canvas.style .width = canvas.style .height = wrapperWidth + "px";?
? ? ? ? ? ? context = canvas.getContext('2d');?
? ? ? ? ? ? centerX = canvas.width / 2;?
? ? ? ? ? ? centerY = canvas.height / 2;?
? ? ? ? ? ? newPercent = 0;?
? ? ? ? ? ? speed = 1;?
? ? ? ? ? ? from = 0;?
? ? ? ? ? ? to = percent;?
? ? ? ? ? ? duration = 1000;?
? ? ? ? ? ? lineWidth = 15;?
? ? ? ? ? ? radius = canvas.width / 2 - lineWidth;?
? ? ? ? ? ? strokeStyle = wrapper.getAttribute('data-cp-color');?
? ? ? ? ? ? start = new Date().getTime();?
? ? ? ? ? };?
?
? ? ? ? ? function animate() {?
? ? ? ? ? ? requestAnimationFrame(animate);?
? ? ? ? ? ? var time = new Date().getTime() - start;?
? ? ? ? ? ? if (time <= duration) {?
? ? ? ? ? ? ? var x = easeInOutQuart(time, from, to - from, duration);?
? ? ? ? ? ? ? newPercent = x;?
? ? ? ? ? ? ? text.innerHTML = Math.round(newPercent) + " %";?
? ? ? ? ? ? ? drawArc();?
? ? ? ? ? ? }?
? ? ? ? ? }?
?
? ? ? ? ? function drawArc() {?
? ? ? ? ? ? var circleStart = 1.5 * Math.PI;?
? ? ? ? ? ? var circleEnd = circleStart + (newPercent / 50) * Math.PI;?
? ? ? ? ? ? context.clearRect(0, 0, canvas.width, canvas.height);?
? ? ? ? ? ? context.beginPath();?
? ? ? ? ? ? context.arc(centerX, centerY, radius, circleStart, 4 * Math.PI, false);?
? ? ? ? ? ? context.lineWidth = lineWidth;?
? ? ? ? ? ? context.strokeStyle = "#ddd";?
? ? ? ? ? ? context.stroke();?
? ? ? ? ? ? context.beginPath();?
? ? ? ? ? ? context.arc(centerX, centerY, radius, circleStart, circleEnd, false);?
? ? ? ? ? ? context.lineWidth = lineWidth;?
? ? ? ? ? ? context.strokeStyle = strokeStyle;?
? ? ? ? ? ? context.stroke();?
? ? ? ? ? }?
?
? ? ? ? ? var update = function () {?
? ? ? ? ? ? getValues();?
? ? ? ? ? ? animate();?
? ? ? ? ? }?
? ? ? ? ? update();?
?
? ? ? ? ? // Update circular progress on input change?
? ? ? ? ? var inputFields = document.querySelectorAll(".percentage-input");?
? ? ? ? ? Array.prototype.forEach.call (inputFields, function (inputField) {?
? ? ? ? ? ? debugger;?
? ? ? ? ? ? inputField.addEventListener("input", function () {?
? ? ? ? ? ? ? // Get values from input fields?
? ? ? ? ? ? ? var TechnicalSkills = parseInt(document.getElementById("TechnicalSkills").value) || 0;?
? ? ? ? ? ? ? var ProblemSolving = parseInt(document.getElementById("ProblemSolving").value) || 0;?
? ? ? ? ? ? ? var CommunicationSkills = parseInt(document.getElementById("CommunicationSkills").value) || 0;?
? ? ? ? ? ? ? var AnalyticalThinking = parseInt(document.getElementById("AnalyticalThinking").value) || 0;?
? ? ? ? ? ? ? var CodingProficiency = parseInt(document.getElementById("CodingProficiency").value) || 0;?
? ? ? ? ? ? ? var DomainKnowledge = parseInt(document.getElementById("DomainKnowledge").value) || 0;?
?
? ? ? ? ? ? ? // Set data-cp-percentage attributes?
? ? ? ? ? ? ? document.querySelector(".counter[data-cp-color='#53405e']").?
setAttribute("data-cp-percentage", TechnicalSkills);?
? ? ? ? ? ? ? document.querySelector(".counter[data-cp-color='#e0bd93']").?
setAttribute("data-cp-percentage", ProblemSolving);?
? ? ? ? ? ? ? document.querySelector(".counter[data-cp-color='#FF675B']").?
setAttribute("data-cp-percentage", CommunicationSkills);?
? ? ? ? ? ? ? document.querySelector(".counter[data-cp-color='#529f82']").?
setAttribute("data-cp-percentage", AnalyticalThinking);?
? ? ? ? ? ? ? document.querySelector(".counter[data-cp-color='#a23658']").?
setAttribute("data-cp-percentage", CodingProficiency);?
? ? ? ? ? ? ? document.querySelector(".counter[data-cp-color='#1e656d']").?
setAttribute("data-cp-percentage", DomainKnowledge);?
?
? ? ? ? ? ? ? // Update circular progress?
? ? ? ? ? ? ? update();?
? ? ? ? ? ? });?
? ? ? ? ? });?
?
? ? ? ? ? var resizeTimer;?
? ? ? ? ? window.addEventListener("resize", function () {?
? ? ? ? ? ? debugger;?
? ? ? ? ? ? clearTimeout(resizeTimer);?
? ? ? ? ? ? resizeTimer = setTimeout(function () {?
? ? ? ? ? ? ? clearTimeout(resizeTimer);?
? ? ? ? ? ? ? start = new Date().getTime();?
? ? ? ? ? ? ? update();?
? ? ? ? ? ? }, 250);?
? ? ? ? ? });?
? ? ? ? });?
?
? ? ? ? function easeInOutQuart(t, b, c, d) {?
? ? ? ? ? ? debugger;?
? ? ? ? ? if ((t /= d / 2) < 1) return c / 2 t t t t + b;?
? ? ? ? ? return -c / 2 ((t -= 2) t t t - 2) + b;?
? ? ? ? }?
?
? ? ? });?
?
? ? ? circleProgress('.counter');?
?
? ? ? function getRandom(min, max) {?
? ? ? ? debugger;?
? ? ? ? return Math.random() * (max - min) + min;?
? ? ? }?
? ? });?
? </script>?
?
Integration with Dynamics 365?
?
To seamlessly integrate the solution with Dynamics 365, the user utilizes a web resource. The setClientApiContext function establishes a connection with the Dynamics 365 form, allowing the user to retrieve and update skill values dynamically. This integration enhances the user interface and provides a streamlined experience.?
?
// This script sets the client API context for Dynamics 365 Model-Driven Apps.?
// It retrieves attribute values from the form context and updates corresponding input fields.?
// It triggers input events to dynamically update progress bars (percentage circles)??
//and performs an overall update.?
?
function setClientApiContext(xrm, formContext) {?
? ? // Debugger statement for debugging purposes?
? ? debugger;?
???
? ? // Assign the provided Xrm and formContext to global variables for wider accessibility?
? ? window.Xrm = xrm;?
? ? window._formContext = formContext;?
???
? ? // Retrieve attribute values from the form context?
? ? var TechnicalSkills = formContext.getAttribute("baelynn_technicalskill").getValue();?
? ? var ProblemSolving = formContext.getAttribute("baelynn_problemsolving").getValue();?
? ? var CommunicationSkills = formContext.getAttribute("baelynn_communicationskills").getValue();?
? ? var AnalyticalThinking = formContext.getAttribute("baelynn_analyticalthinking").getValue();?
? ? var CodingProficiency = formContext.getAttribute("baelynn_codingproficiency").getValue();?
? ? var DomainKnowledge = formContext.getAttribute("baelynn_domainknowledge").getValue();?
???
? ? // Set values to the percentage-input fields?
? ? document.getElementById("TechnicalSkills").value = TechnicalSkills;?
? ? document.getElementById("ProblemSolving").value = ProblemSolving;?
? ? document.getElementById("CommunicationSkills").value = CommunicationSkills;?
? ? document.getElementById("AnalyticalThinking").value = AnalyticalThinking;?
???
? ? // Trigger input events for newly added fields?
? ? document.getElementById("CodingProficiency").value = CodingProficiency;?
? ? document.getElementById("CodingProficiency").dispatchEvent(new Event("input", { bubbles: true }));?
???
? ? document.getElementById("DomainKnowledge").value = DomainKnowledge;?
? ? document.getElementById("DomainKnowledge").dispatchEvent(new Event("input", { bubbles: true }));?
???
? ? // Perform an overall update?
? ? update();?
? }?
???
?
2) Create OOB Form and Add HTML Web Resource?
?
Create a new Out-of-the-Box (OOB) form in Dynamics 365 or use an existing one. Inside the form designer, add an HTML web resource component.?
a. Select the Diagram.html file.?
?
3) Identify HTML Web Resource Name & Update Form_onload.js??
Inside the OOB form, the HTML web resource will be assigned a name. Copy the name.?
Replace "WebResource_name" in the form_onload script with the actual name of the HTML web resource.?
?
4) Upload JavaScript File to Form Events?
Upload the "Form_onload.js" file to the form events:?
?? ?OnLoad: Attach the "Form_onload.js" script to the form onload event.?
?? ?OnChange: Attach the "Form_onload.js" script to the onchange event of required fields.?
?? ?OnSave: Attach the "Form_onload.js" script to the form onsave event.?
?
Usage and Customization?
?
Users can effortlessly interact with the dynamic percentage circles on the Dynamics 365 form. Input fields allow for customization, enabling users to update skill percentages on the fly. The flexibility extends to customization options such as changing circle colors or adjusting dimensions to suit specific preferences.?
?
Practical Example?
?
Consider a scenario where a user needs to evaluate and showcase their skills on a Dynamics 365 platform. The dynamic percentage circles offer a visually engaging way to represent technical skills, problem-solving abilities, communication skills, analytical thinking, coding proficiency, and domain knowledge. This not only enhances the user experience but also provides a clear and intuitive representation of skills.?
?MERP Systems, Inc. employs the Microsoft Power Platform, which not only boasts a wide array of features and capabilities but also provides a scalable and seamlessly extensible foundation. Power BI empowers business users to craft visually compelling, drill-down dashboards, while Power Apps facilitates the creation of feature-rich model-driven applications with highly intuitive low-code apps and custom pages that seamlessly blend the functionalities of both approaches, offering a hybrid solution. Advancements in AI/ML integration can be seamlessly achieved through the Power Automate framework, presenting a compelling alternative to Robotic Process Automation (RPA). Additionally, deploying virtual agents and chatbots is made straightforward using Power Virtual Agents. The potential for the Dataverse platform to interact with ChatGPT opens up uncharted possibilities in the marketplace. Whether it involves modernizing legacy applications, upgrading retiring ones, or completely revamping large-scale systems, the Low Code SWAT approach provides a pragmatic and successful path.
If you are interested in discussing further about our approach and techniques for enterprise digital transformation, please contact us at [email protected]