How to Make a Circular Progress Bar Pluggable Widget

How to make a Circular Progress Bar Pluggable widget

It’s not often I get the chance to create pluggable widgets in my day to day, so to keep my skills fresh, I like to challenge myself by trying to recreate a widget that I use regularly. It’s been a while since I’ve done this, so this week I decided I would test out my skills and try to recreate the Circular Progress Bar widget, which comes in the Native Mobile resources Modules. So strap in and let’s go on a journey of discovery in the world of React and Mendix.


Before we start

Please take a moment before proceeding, to make sure you have everything setup before we begin. You will need:

  • Mendix Studio Pro (I used (9.1.1) in this example.
  • Node js installed and configured on your machine.
  • Your IDE of choice, I used VS Code for this.
  • It is recommended you have completed this tutorial on pluggable widgets from the documentation or you have completed this academy course.
  • The Make it Native 9 App installed on your testing device

Choosing a a github library to use

Knowing where to start with a project like this can sometimes be daunting to a developer, that’s why I find it’s a lot easier to know what to do after some research. So I went to github and looked what was already out there. I came across this repo from epicode-academy and decided to use this as a base for my component. Theres some other projects in this repo, so if you’re looking for the exact link its here.

Analyzing the code

Before starting to code, it’s important to actually review the code provided and making sure you understand what is happening. Looking at the example, I can already see that this uses functional components, and I know this will require some alteration in order to be implemented in a pluggable widget as the main component is normally a class component.

Components come in two types, Class components and Function components. A functional component is just a plain JavaScript function which accepts props as an argument and returns a React element. A class component requires you to extend from React.

Creating the widget scaffold

Enough reading, let’s code! To start off open up your terminal and navigate to your project directory. A quick way to do this is to click “Show App Directory in Explorer” under “App” in Studio Pro’s top menu. This will open up a file explorer with your app’s files in it. Copy the file path in the navigation bar and then go back to the terminal you opened. Type in “cd” and paste in your file path like this:

cd yourFilePath

Next up we have to create a folder to store the Custom widgets. In your terminal type:

mkdir CustomWidgets

And then use cd to go into the folder you just created:

cd CustomWidgets

Now you can use the widget builder to create the widget scaffold for you. In the terminal again, use this command to call the Mendix widget generator:

@mendix/widget CircularProgressBar

The widget generator will then guide you through the creation of the widget by asking some questions, here’s what I used:

  • Widget name: {Your widget name}
  • Widget Description: {Your widget description}
  • Organization Name: {Your organization name}
  • Copyright: {Your copyright date}
  • License: {Your license}
  • Initial Version:{Your initial version number}
  • Author: {Your author name}
  • Mendix Project path: ../../
  • Programming language: Javascript ES6
  • Widget type: Native Mobile
  • Widget template: Empty widget (recommended for more experienced developers)
  • Unit tests: No
  • End-to-end tests: No

Setting up the widget XML

The first thing we need to do is to alter the widget XML so that we can receive data from the context entity in the form of an attribute. To do this we need to create a property which can accept an integer value, it’s also important to define the key value for this property. You can see my widget XML below.

<?xml version="1.0" encoding="utf-8"?>
<widget id="mendix.circularprogressbar.CircularProgressBar" needsEntityContext="true" offlineCapable="true" pluginWidget="true"
supportedPlatform="Native" xmlns="http://www.mendix.com/widget/1.0/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../node_modules/mendix/custom_widget.xsd">
<name>Circular Progress Bar</name>
<description>Animated Circle Progress widget</description>
<icon>
iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABp1BMVEUAAABV//9mzP9LtP9Ms/9Jtv9NsvdJsfpLtPpJsfdJsfhJsvhJsvdKsvdJsPhKsPhJsfdJsPhJsfdIsfhJsfdIsPdJsfhJsfhJsPhJsPhIsfhIsPdJsPdKsPdKsfdNsvdOsvdPs/dQs/dRtPdStPdTtPdUtfdWtvdXtvdauPdcuPdeufdeufhguvhiu/hju/hkvPhmvfhnvfhpvvhrv/huwPhvwfhxwfhywvhzwvh4xfl5xfl6xfl8xvl9xvl9x/mByPmCyfmFyvmGyvmJzPmKzPmLzfmNzvqPzvqQz/qT0PqU0PqU0fqX0vqY0vqa0/qe1fqg1vqj1/uk1/un2fup2vut2/uv3Puw3Puw3fuz3vu13/u23/u34Pu44Pu64fu64fy84vy94vy+4/y/4/zD5fzE5fzG5vzH5vzI5/zK6PzL6PzR6/zT7P3U7P3V7f3W7f3Y7v3Z7v3c8P3e8f3f8f3g8f3i8v3l8/3l9P3n9P3r9v7t9/7u9/7v+P7w+P7x+f7y+f70+v71+v74/P75/P76/f77/f78/f78/v79/v7+/v7////6dMsRAAAAG3RSTlMAAwURGxwhMTNic3SEh4iVp7XBzejt7vH5/f6PsMNWAAABsklEQVR4AWIYfGAUjIJRMAqYuYREJKWJAqLCPGwY+jnFpEkBEryMqPr5pEkFgkwo9kuTDviR/S9GhgFSHAgDuKXJAQIIA4TIMkAcEY4i0mQBVrgBkuQZwA43QJo8wIFhQEhEOIBQOutHJozDOP5Crp4e1RhkJ0tKGJFd6oNEdtmJyEIzpaZl5nrRZgaHM/2Pf5/vwXXfyagXgG93bwSAlEolowLMm9w83gibhXH2gKKVdD67gTnWjwCk+VVjMQS4suSnnjMLRVFc9sAHvAX2A9fySaXNBMbEZVUWscaHIMRuqwBgD8hDEbnsRmfjUKJkAQZGCTlO/xWBwIADQLIZBlY441MvfoF1xlFS/4fy+bzXKh4dgNJE7L3eh3tmtuWa+AMcMIY3dgUvZQpGEYmMw2kD7HC+R29UqyoXLaBd0QZxzgXgikLLDSqJTKU5HOcS0MsbA9jPqtwCRvXm2eorBbNIJBw3KJ9O4Yl+AAXdnyaLt7PWN3jRWLvzmAVp94zO5+n41/onfo/UpExxZqI0O7NQr0DhIq9Io7hQpbRYp7hiobRqo6ByFcNWuY6CUTAKRgEAo8X0lBD3V30AAAAASUVORK5CYII=
</icon>
<properties>
<propertyGroup caption="General">
<property key="progress" type="attribute" required="true">
<caption>Progress Indicator</caption>
<description>The attribute that contains the circularprogressbar value, should be an integer between 0 and 100</description>
<attributeTypes>
<attributeType name="Integer"/>
</attributeTypes>
</property>
</propertyGroup>
</properties>
</widget>

The main component

Now that we have the data input defined, we can work on the main component. The main component in this case is just intended to render the child component as well as pass it the data provided by use of its props.

“Props” is a special keyword in React, which stands for properties and is being used for passing data from one component to another. But the important part here is that data props are being passed in a uni-directional (one way) flow only.

Something to note here is that as stated previously, the Main component is created in the scaffolding as a class component. This means we need to alter the Github code slightly.

Heres the code for my main component called CircularProgressBar.

import { React,Component ,createElement} from "react";
import CircularProgress from "./components/CircleComponent";
export class CircularProgressBar extends Component {
constructor(props){
super(props);
this.handleChange =this.handleChange.bind(this);
const {progress} = this.props;
console.log('constuctor triggered');
}
render() {
const {progress} = this.props;
console.log('render triggered');
return (
<CircularProgress
progress={progress.value}
size={200}
/>
)
}
}

The child component

The child component is where the bulk of our logic takes place. Here we accept the data from the parent component using props once again, a bit of logic and the styled library from ‘styled-components/native’ to style the individual pieces which make up the progress bar. Finally we end off with a return statement which should render the component. Check the Child Component “CircleComponent” code below.

import React, { useRef, useEffect, createElement } from "react";
import styled from 'styled-components/native';
import {Animated} from 'react-native';
const EmptyColour = '#a0a0a1';
const ProgressColour = '#0085ff';
const CircleBase = styled(Animated.View)`
width: ${props => props.size}px;
height: ${props => props.size}px;
border-radius: ${props => props.size / 2}px;
border-width: ${props => props.size / 10}px;
`;
const EmptyCircle = styled(CircleBase)`
border-color: ${EmptyColour};
justify-content:center;
align-items: center;
transform: rotate(-45deg);
`;
const Indicator = styled(CircleBase)`
position: absolute;
border-left-color:${ProgressColour};
border-top-color:${ProgressColour};
border-bottom-color:transparent;
border-right-color:transparent;
`;
const CoverIndicator = styled(CircleBase)`
position: absolute;
border-left-color:${EmptyColour};
border-top-color:${EmptyColour};
border-bottom-color:transparent;
border-right-color:transparent;
`;
export default function CircularProgress(props) { //added input props
const {progress, size} = this.props //destructured the props
console.log (styled)
const animatedProgress = useRef(new Animated.Value(0)).current;
const animateProgress = useRef(toValue => {
Animated.spring(animatedProgress, {
toValue,
useNativeDriver: true,
}).start();
}).current;
useEffect(() => {
animateProgress(progress);
}, [animateProgress,progress]);
const firstIndicatorRotate = animatedProgress.interpolate({
inputRange: [0, 50],
outputRange: ['0deg', '180deg'],
extrapolate: 'clamp',
});
const secondIndicatorRotate = animatedProgress.interpolate({
inputRange: [0, 100],
outputRange: ['0deg', '360deg'],
extrapolate: 'clamp',
});
const secondIndictorVisibility = animatedProgress.interpolate({
inputRange: [0, 49, 50, 100],
outputRange: [0, 0, 1, 1],
extrapolate: 'clamp',
});
return (
<EmptyCircle size={size}>
<Indicator
style={{transform: [{rotate: firstIndicatorRotate}]}}
size={size}
/>
<CoverIndicator size={size} />
<Indicator
size={size}
style={{
transform: [{rotate: secondIndicatorRotate}],
opacity: secondIndictorVisibility,
}}
/>
</EmptyCircle>
);
}

Installing dependencies

Its almost time to test out your widget, but we have a few things to take care of before we do. We have to make sure any libraries we’ve made use of are correctly imported into our widget folder. Open up your terminal again and type the command.

npm install --save

Wait for it to download and install any dependencies your code might need.

If anything goes wrong or you need to redo this, you could also perform a clean install which removes all node modules and reinstalls them with this command (but only do this if 100% needed!)

npm ci --save

Creating a widget .mpk file

To bundle your widget and create an .mpk which can be used in your Mendix project, run the following command in your terminal :

npm run build

This action will build your widget code and then copies the widget into your widgets folder. The final step is to synchronize your app directory in studio pro, either by pressing F4 or go to “App” →”Synchronize App Directory” in Studio Pro’s top menu.

Now you can access your widget when developing in Studio Pro. The widget will need to be placed inside a dataview for context and will expect an integer attribute to be connected to it, but once you’ve set this up you can run and test out your new widget using the Make it Native 9 app.

Read more