- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
In Part 2 we created a few more files, and built a few React Components.
I noted though, that the CSS was ‘inline’ – So it was mixed with our HTML.
I did this for speed, but it’s best to separate and this will demonstrate importing files using the IDE, so let’s do that.
Move CSS to a file, and import it in the ServiceNow IDE.
We have this CSS for App.tsx for our ‘RecordTile’ component:
<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>
);
So let’s take an extract from the above:
<div style={{
border: '1px solid #ccc',
borderRadius: '8px',
padding: '16px',
margin: '8px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
textAlign: 'center'
}}>
So we’ve got CSS here (mixed with our HTML, not ideal), which is adding border and a radius (curved edges) etc to our Div (boxes).
Let’s move that code to a new file, called ‘myCss.css’ and then put that CSS code there and then reference it from the HTML.
However we can’t just copy and paste, we need to first create a ‘className’ for this DIV. Then our CSS can be ‘linked’ to that className, then when we import the code it all fits together.
- The file gets imported,
- it matches a className,
- then the CSS applies to that className (Div) making it pretty…ish. 😅
The adjusted CSS (in our new ‘myCss.css’ file within the CLIENT folder):
.divStyling {
border: 1px solid #ccc;
border-radius: 8px;
padding: 16px;
margin: 8px;
box-shadow:0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
And the import, within the App.tsx file at the top:
import './myCss.css';
And the (same) DIV, using it (Previously this was ‘styles=’):
<div className='divStyling'>
There are some slight differences in the CSS due to react.
- We moved from camelCase (e.g. BorderRadius, to border-radius in our CSS file). This is because of Javascript, out of scope really for this article but worth noting.
- We don’t need the ‘’ around the CSS values – E.g. text-align:’center’ becomes text-align:center;
The final CSS for this step looks like this:
Click ‘Build and install’, refresh the UI page.
Ok our CSS is still maintained, and looks good!
We can do the rest, but I’ll skip this as its just repeating the above really (with meaningful classnames, etc)
----
Let’s test module imports, can we grab a NPM Package and prove that works
I’m not yet sure this is the best route, but I added:
"moment": "2.30.1"
To my package.json file (typically I would run npm install in the terminal – but I can’t see that the IDE? Please jump into the comments if you’re aware of it? I suspect instead I'd need to use the SDK and Visual studio code but that'll be a separate topic)
Also note:
In the package.json file you may see 'devDependencies' and 'dependencies' - You want 'moment' in the latter so that it would be installed when you install your application/installed to the sys modules table of your instance.
The IDE detects, and asks to install them, yes please! (It's a little backwards perhaps, but it works)
Then in our App.tsx file at the top just add:
import moment from 'moment';
We should be good to go!
Let’s check…
Below the <h1> tag in the App.tsx I add this 2nd line:
<h1 className="header">ServiceNow Dashboard - ReactJS</h1>
{"Current time and date:" + moment().format()}
It should, show us the current date. Why?
- Moment is imported with our import statement,
- then we run format() method (on the moment object) and that returns a string and since we’re inside the { curly braces } we jump into the ‘javascript land’ of React and magic… the time appears on our page!
Click build and install, wait around 5 seconds then reload your UI Page and it should work for you too!
So today we:
- Got imported CSS files working – This allowed us to separate our code, keep things clean.
- Got NPM Packages working - This opens a whole heap of code we can leverage which will save us all significant amount of time.
These 2 achievements open a world of possibilities that I’ll leave you to explore.
In the next part I think we’ll expand upon the application but that’s mostly ReactJS code and I wanted to cover Fluent and the IDE here.
Hope this helps!
Thanks all
The current App.tsx file:
import React, { useState, useEffect } from 'react';
import './myCss.css';
import moment from 'moment';
// 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 className='divStyling'>
<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">
<div className="testCss">Hey Im testing CSS</div>
<h1 className="header">ServiceNow Dashboard - ReactJS</h1>
{"Current time and date:" + moment().format()}
<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;
}
}
The new myCss.css file:
I added the .tssCss class also, to give another example and keep it simple to ensure the imports were working.
.testCss {
background-color: red;
color: blue;
}
.divStyling {
border: 1px solid #ccc;
border-radius: 8px;
padding: 16px;
margin: 8px;
box-shadow:0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
The folder structure:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.