- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
Hello,
So in part 1 – we got a basic reactJS working in UI page via the IDE.
I played around a bit more with the IDE and created this:
It gives a summary of incidents, changes, problems in some tiles – A mini dashboard!
Nothing ground breaking I know, but it combines TypeScript + ReactJS + CSS all via the IDE in ServiceNow and gets us data from the platform, without any complexities around authentication etc. A good starting point.
For this, I wrote the following code (just change the App.jsx file, from what we had in Part 1).
I've added comments to the code below to explain some bits.
import React, { useState, useEffect } from 'react';
// TypeScript interfaces - We want to get g_ck and keep typescript happy.
//You can learn typescript here: https://www.typescriptlang.org/docs/
//Honestly, it's great, but the errors youll encounter be frustrating... keep going though!
//
declare global {
interface Window {
g_ck: string;
}
}
// Reusable Tile Component - More Typescript stuff here. Keeps it happy.
interface TileProps {
title: string;
tableName: string;
listUrl: string;
}
const RecordTile: React.FC<TileProps> = ({ title, tableName, listUrl }) => {
//The stuff after RecordTile: is more typescript.
//JS would just be:
// const RecordTile = ({ title, tableName, listUrl }) => {
const [count, setCount] = useState<number | null>(null); //ReactJS State - Updates to state, cause ReactJS to 'rerender' the page so the visual updates occur.
const [loading, setLoading] = useState(true);
//useEffect - This function will run on mount (when the page loads), but also when 'tableName' changes in the future.
//For now, just think of is as 'On load... Do this...
useEffect(() => {
async function fetchCount() {
try {
setLoading(true);
const result = await getRecordCount(tableName);
setCount(result);
setLoading(false);
} catch (error) {
console.error(`Failed to fetch ${tableName} count:`, error);
setLoading(false);
}
}
fetchCount();
}, [tableName]);
//React returns HTML (well, 'JSX') - It's like JS and HTML had a baby! ReactJS will render this for us.
return (
<div style={{
border: '1px solid #ccc',
borderRadius: '8px',
padding: '16px',
margin: '8px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
textAlign: 'center'
}}>
<h2 style={{ fontSize: '20px', fontWeight: 'bold', marginBottom: '8px' }}>{title}</h2>
{loading ? (
<p>Loading...</p>
) : count !== null ? (
<a
href={listUrl}
style={{
fontSize: '24px',
fontWeight: 'bold',
color: '#007bff',
textDecoration: 'none'
}}
onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
>
{count}
</a>
) : (
<p>Error loading count</p>
)}
</div>
);
};
// Main App Component - This is our 'main' function. the main.tsx file references this function in the <App> tag. Causing this function to run, and the stuff it returns
//goes where <App> is.
export default function App() {
const handleButtonClick = () => {
console.log('Button clicked! ReactJS in ServiceNow is working!');
};
return (
<>
<style>{`
.container {
padding: 16px;
}
.header {
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.button {
margin-top: 16px;
background-color: #007bff;
color: white;
font-weight: bold;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button:hover {
background-color: #0056b3;
}
`}</style>
<div className="container">
<h1 className="header">ServiceNow Dashboard - ReactJS</h1>
<div className="grid">
{/* The below are function calls to the RecordTile function - We pass in parameters, by using the HTML attributes.
E.g. tableName="incident" is the TableName parameter used above in the RecordTile function. Try adding your own, e.g. Incident tasks perhaps.
*/}
<RecordTile title="Incidents" tableName="incident" listUrl="/incident_list.do" />
<RecordTile title="Problems" tableName="problem" listUrl="/problem_list.do" />
<RecordTile title="Changes" tableName="change_request" listUrl="/change_request_list.do" />
</div>
{/* Below is a HTML Button, but the onClick is pointing to a react function we declared above: handleButtonClick
We put it into curlys {} to go into 'Javascript land' and call a function etc */}
<button
className="button"
onClick={handleButtonClick}
>
Refresh Dashboard
</button>
</div>
</>
);
}
// Fetch function for record count
async function getRecordCount(tableName: string): Promise<number> {
console.log(`Fetching ${tableName} count...`);
try {
const searchParams = new URLSearchParams();
searchParams.set('sysparm_count', 'true');
const response = await fetch(`/api/now/stats/${tableName}?${searchParams.toString()}`, {
method: 'GET',
headers: {
Accept: 'application/json',
'X-UserToken': window.g_ck,
},
});
if (!response.ok) {
const errorData = await response.json();
console.error(`Error fetching ${tableName} count:`, errorData?.message);
return 0;
}
const { result } = await response.json();
return result.stats.count || 0;
} catch (error) {
console.error(`Error fetching ${tableName} count:`, error);
return 0;
}
}
I’ve used inline CSS, as Fluent does not yet support CSS modules as mentioned here
- You can learn more about ReactJS here: https://react.dev/learn
- TypeScript here: https://www.typescriptlang.org/docs/
In the next post, we’ll expand upon this and get the refresh button working.
I’ll also dive into seeing how best we can use CSS in the IDE as having it mixed with our HTML isn’t ideal.
I hope this helps give some guidance though, a little boilerplate perhaps!
Cheers all 😊
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.