Introduction
In this blog post I show you how to take the first steps in leveraging the Giraffe SDK to develop your own app. Apps allow Giraffe organisions or data providers to build custom interfaces or functionality, examples could include:
Building an integration to your enterprise project system to integrate project information
Building an engagement app to aid stakeholders understand your plans draw in Giraffe
Or an interface to your data products or services so Giraffe users can access them
I will outline how to take your first steps to get setup to develop an app and break down the ways to interact with the Giraffe SDK while working on a project I am delivering at the moment.
My name is Luke Bassett and I am a Principal solutions consultant at Giraffe, and my background has been supporting geospatial and digital solutions for government. While I have done some basic programming in my career, I am not an expert, so whether you are or are not, I hope these instructions help you familiarize yourself building your own app in Giraffe and help you get an idea of what you could achieve using the Giraffe SDK and Giraffe Apps.
The app I am building
The aim of the app we are building today is an engagement app in which the user wishes to use Giraffe as a public engagement tool for a new environmental policy. The app will allow users to flick through cards explaining the different policy spatial layers, and the user of the app can either search for land parcels or click on the map to get information about how they are effected.
The SDK within Giraffe allows control over or can access:
The Giraffe project layer panel - what is on, off, and even views published in the project
Where the user is clicking or what is happening in the Giraffe main window - this is called the GiraffeState
It can also prepare HTML within the app window which can draw in all sorts of create html and functions to make a rich experience
It can also interface with external systems APIs to transform content inside of Giraffe - and import external data feeds / products into the Giraffe project - which can then be used to enhance the project spatial context, or feed into the Giraffe analytics
The final app template will be available in our public repository as a template
Getting started with react app development
For those who are new to development it can take some guidance to setup your development environment and work on a general process for developing react apps.
Here are some steps to follow:
Get a ChatGTP or similar service account - even a free one will do
Vibe Coding gets a bad rap but from my experience when I used to do more technical work we would always be googling how to do things (and before that technicians would be referring to physical reference manuals). It is worthwhile going with the modern flows as it does help and provides a multiplication of efficiency, particularly with things you are not 100% across all the technical details.
My tip is to maintain chats that are about certain technical topics such as have one specific for react web development as the AI will get used to what you are trying to achieve and provide better responses. I will provide examples throughout this post. Don’t ask it to write the entire app in one go, just do bite size chucks on the functionality you want to add bit by bit and provide some description on the languages you are using, how it should look, what data should feed into it, and what the output should be. Providing code examples is also a great thing to do.
Also, if you get stuck on any of the steps below you can ask ChatGTP how to solve the issue.
Install the development apps on your desktop (open toggle to see)
VSCode - this is a modern development environment application
Node - this is a JavaScript tool that does a few things importantly gives you access to npm
Vite - this is a web server which will allow you to publish apps to the browser from your device. You can install Vite from within VSCode using the terminal for a project.
When you install Vite you will choose a folder location where you wish to build your apps and you will chose your programming languages - in this case we will use React and TypeScript. I show examples of this later.
Get familiar with HTML, React and JavaScript / Typescript
It is worthwhile doing some youtubing or do a LinkedIn Learning course to understand the basics of HTML, React, JavaScript, and Typescript.
Don’t feel like you need to do a 6 month course, just a few hours online or each area should be enough. These days you don’t need to be a pro you just need enough information to be able to interpret what it is doing so you can ask ChatGTP to build code for you, and you have a rough understanding what it does so you can ask for revisions to meet your needs.
Be Familiar with Giraffe
Apps within Giraffe integrate with the core Giraffe product so I recommend being familiar with how it works such as projects, layers, drawings, analytics, user authentication. The Giraffe Knowledge Base is a great resource to learn about the app.
There are also great pages on the KB to learn about the Giraffe SDK and various ways to interact with Giraffe programmatically.
Set up a Giraffe project, and link your app development environment
You can follow these steps to launch an app window and configure it to link to your Vite server and development environment.
Have an idea on how to publish your app
To publish an app, it will need to be built and hosted somewhere and the general term for this is a CI/CD pipeline. If you need support on this reach out to Giraffe team or your CTO / IT Dept.
Luckily you don’t need this if you want to begin, you can leverage the iFrame to view your app from your Vite development environment. And if the code is all in one tsx file you can host it in our livecode app until you establish your own hosting and pipeline.
Establishing My Vite App
For a fresh app you need a folder on your device, and then you either clone an app from a repository (such as our public repo), or use Vite to deploy the standard app template. Here are the steps for the standard app template:
In VSCode I open the terminal and navigate to the new app folder, then type
npm init vite
It will ask you to select your framework (react), and variant (I use Typescript + SWK)
You can then launch your template app using npm run dev and it will tell you the url and port to use in our configuration.
cd (your app dir) npm install npm run dev
My local development server is publishing the default app into http://localhost:5173. I can enter these into the Giraffe iFrame configuration and now the default app is in Giraffe.
You can also see VSCode shows template app files which are being published live into our app. We can begin to write code and save it - it should then update the Giraffe app right away each time we save the code (unless it is broken).
The main file we will update initially is \src\App.tsx which is the primary React Typescript file for the application. We can create more tsx or ts files based on the complexity of our application, and then import the functions in those files via the import method as seen at the top in my app.tsx.
App Requirements
As noted above the app we will build if for engagement it will have the following functional components
A search to identify land parcels
The user can click on the map and a parcel is selected
A pop up shows when a parcel is selected
Cards in the iFrame app window showing content the user can expand or close showing contextual paragraphs
When cards are opened / closed it will turn on and off specific map layers which will be controlled by Giraffe Views (where a user can control what layers are on or off)
I was provided an example of how to do the search, this came in a tsx file, but it will require some modification to get it working for the purposes of this app as it needs to work in combination with the user clicking on the map to select a parcel.
Giraffe SDK
To achieve this functionality, I am going to use different SDK components as well as some open Javascipt and React functions to build my app. It is good to have an understanding of the different SDKs
Giraffe SDK | calls | Description |
iFrame-SDK (@gi-nx/iframe-sdk) | giraffeState | Access Giraffe data about projects, layers, views |
| rpc | Run SDK functions |
iFrame-SDK-react (@gi-nx/iframe-sdk-react) | useGiraffeState | Another method to interact with giraffeState when leveraging React webhooks (useEffect etc) |
Using the SDK to build my App
I will work through each one by one in the following steps - expand each one to see the examples of how I achieved the results leveraging the SDK calls
App HTML Content - Accordian Text
Kicking off
The first part is to build out the html display components of the app. I am going to begin with the card display. My CTO also noted there was a good react library for it. And he noted that the SHADCN tools require some setup to get them working - I followed the npm steps to install the accordion library. Also note that the accordion library requires me to edit some configuration files - follow these steps
Installing libraries
If you Vite app is running in Terminal you shut it down, then run the install process, then run it again by running npm run dev.
Configuring the accordion via ChatGTP
This was the beginning of this ChatGTP chat so I called it ‘react development’ and put all my questions into the one chat so it remembered what I was going to achieve, and it could steer me in the right direction as I worked my way through the entire solution.
Back to the accordion above - the example here suggested I create a new tsx file for the accordion text however I ignored that and just put it into the primary app.tsx, and I overwrote the html content being created by the default app (with some guidance from chat gpt)
import { useState } from 'react'
import './App.css'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
function CategoryAccordion() {
return (
<Accordion type="single" collapsible className="w-full max-w-md mx-auto">
<AccordionItem value="item-1">
<AccordionTrigger>Category 1</AccordionTrigger>
<AccordionContent>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque vel ullamcorper nulla. Cras id volutpat odio.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Category 2</AccordionTrigger>
<AccordionContent>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis egestas, nisi in blandit facilisis, velit libero convallis lacus.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>Category 3</AccordionTrigger>
<AccordionContent>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam.
</AccordionContent>
</AccordionItem>
</Accordion>
)
}
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<main className="p-6">
<CategoryAccordion />
</main>
</div>
</>
)
}
export default AppAnd this is what it looked like - great - some work to do on the styling but fully functional html interactive cards.
The styling of these items come from a few locations, and it is good to understand where before you begin prompting chatgtp about it as chatgtp will not understand the overall structure of the app and where style is being inherited from. A good way to understand is use the browser inspect tool and look at the computed style. You can drill into components. The usual locations are the apps index.css for colours, padding, but the fonts and alignment may be coming from the html class definitions.
CSS is one thing I don’t like to do, and it took a lot of prompting to get something l liked. Here are some sample prompts I made
I won’t dwell too much more on the html components of the app. Check out the repository for the final app configuration.
The next step is to integrate it with the views on the map
Link Accordion to Giraffe View (via iFrame-SDK)
The accordion cards allow for functions to be triggered when the value changes via the settings ‘value’ and ‘onValueChange’.
First steps with iFrame SDK
So now to write a function that hits our SDK to change the Giraffe View. For this we use the Giraffe iFrame SDK to get the GiraffeState (what is happening in the app) and run some Remote Procedure Calls (a.k.a. rpc). To use these install the IFrame SDK into the app
npm install @gi-nx/iframe-sdk
We then import it into our App.tsx
import { giraffeState, rpc } from "@gi-nx/iframe-sdk";And a good way to go is to log them into the console to see what data and functions you have to play with, particularly with the giraffeState.
console.log(giraffeState)
Now when you run the app in the browser you can see what data is available in the browser console
So we will use the GiraffeState to interact list our views, and the RPC allows us to call the SDK functions such as activateViewLayers. Activate View layers needs to be passed the view id like this:
rpc.invoke("activateViewLayers", [view]);Use ChatGTP to help configure the functionality
After some prompts, I built this function to trigger the SDK call activeViewLayers
const handleAccordionChange = (value: string) => { setOpenItem(value); const views = giraffeState.get("views") || []; views.forEach(view => { if (view.name === value) { rpc.invoke("activateViewLayers", [view]); } }); };which is called by the Accordion
<Accordion type="single" collapsible value={openItem ?? undefined} onValueChange={handleAccordionChange} className="w-full max-w-screen-lg mx-auto text-left" />To quote chatGTP
Select land parcels on the map and highlight them (via iFrame-SDK)
The Giraffe map has a spatial land parcel layer hosted within an ESRI MapServer Rest service. These services allow us to query them and get return data which will be used later in the app.
The functionality I am going to attempt is to read when a user is clicking somewhere on the map, that will then call the feature service based on the coordinates we retrieve from SDK call mapSelectedCoord
After some prompting, I created this react hook to setup a listener for map clicks
useEffect(() => { giraffeState.addListener(["mapSelectCoord"], async () => { const coord = giraffeState.get("mapSelectCoord"); // [lng, lat] if (!coord) return; // do something with the coord }, []);I wrote this function to call the MapService to get the cadid based on the coords from the map click event
const fetchCadastreFeature = async ([lng, lat]: [number, number]) => { const url = new URL("<https://maps.six.nsw.gov.au/arcgis/rest/services/sixmaps/Cadastre/MapServer/0/query>"); url.searchParams.set("geometry", `${lng},${lat}`); url.searchParams.set("geometryType", "esriGeometryPoint"); url.searchParams.set("inSR", "4326"); url.searchParams.set("spatialRel", "esriSpatialRelIntersects"); url.searchParams.set("outFields", "cadid"); url.searchParams.set("returnGeometry", "false"); url.searchParams.set("f", "json"); const res = await fetch(url.toString()); const data = await res.json(); return data?.features?.[0]?.attributes?.cadid ?? null; };The final component is I want the parcel to highlight on the map, this uses the SDK method to create a temp layer called ‘Your Property’ and adds the feature as a geojson to the layer. It gets the parcel json result from the ESRI MapServer again but passes the feature id.
export const highlightResponseOnMap = async ({ cadId }: { cadId: string }) => { const queryUrl = `https://maps.six.nsw.gov.au/arcgis/rest/services/sixmaps/Cadastre/MapServer/0/query?where=cadid='${cadId}'&outFields=lotnumber,plannumber,sectionnumber,planlotarea&returnGeometry=true&f=json`; const response = await fetch(queryUrl); const data = await response.json(); const feature = data.features[0]; const geojson = esriToGeoJSON(feature, feature.attributes); const bounds = bbox(geojson); const center = [ bounds[0] + (bounds[2] - bounds[0]) / 2, bounds[1] + (bounds[3] - bounds[1]) / 2, ]; rpc.invoke("addTempLayerGeoJSON", [ "Your Property", { type: "FeatureCollection", features: [geojson], }, { type: "line", paint: { "line-color": "#ff0000", "line-width": 2 }, }, ]); rpc.invoke("fitBounds", [bounds, { padding: 200, duration: 1000 }]); ;And finally, I merged the calls into my useEffect to enact them after each click
useEffect(() => { giraffeState.addListener(["mapSelectCoord"], async () => { const coord = giraffeState.get("mapSelectCoord"); // [lng, lat] if (!coord) return; const cadid = await fetchCadastreFeature(coord); if (!cadid) { console.warn("No cadastre feature found at selected point"); return; } await highlightResponseOnMap({ cadId: cadid }); }); }, []);Here is the result. I will re-use the highlight method for the search as well.
Parcel search function
Next up is the search function which will allow a user to enter an address, and the parcel details.
The NSW planning portal contains the following services which we will hook into.
<https://api.apps1.nsw.gov.au/planning/viewersf/V1/ePlanningApi/address?a=(address)> <https://api.apps1.nsw.gov.au/planning/viewersf/V1/ePlanningApi/lot?l=>(lot details)
The input forms for the search form will use the library react-select/async and allow
The basic pattern for this is to have the form call the service as shown below. The lot search was similar, but it had a tabs for the different parcel descriptions. Overall this pattern could be used for other jurisdiction end points as well.
On completion the form launches the highlight SDK function we built in the earlier step.
const AddressSearch = ({ highlightResponseOnMap, clearHighlight }) => { const loadOptions = useDebouncedCallback((input, cb) => { if (!input) return; fetch( `https://api.apps1.nsw.gov.au/planning/viewersf/V1/ePlanningApi/address?a=${encodeURIComponent( input.toLowerCase() )}&noOfRecords=10` ) .then(r => r.json()) .then(r => cb(r)); }, 600); return ( <AsyncSelect isClearable menuPlacement="top" placeholder="Search Address" cacheOptions loadOptions={loadOptions} getOptionLabel={option => option.address} defaultOptions onChange={option => { if (option) { highlightResponseOnMap(option); } else { clearHighlight(); } return option; }} /> ); };Pop Up (via SDK)
To generate a popup we have a SDK function called ‘addHtmlPopup’ and it is fed some html and lat long coordinates.
await rpc.invoke("addHtmlPopup", [popupHtml, center, {}]);I am going to add this entire function into the highlightResponseOnMap function.
The HTML for the popup content includes fields I am extracting from the ESRI services for the selected parcel. You may use any html to structure and style the results. Mine is pretty simple at the moment.The centroid latitude longitude coordinates are generated from the bounding box of the feature outline. Using the geojson library we can extract the bounding box and do some math to determine the center.
const lotBbox = bbox(lot); rpc.invoke("fitBounds", [ lotBbox as LngLatBoundsLike, { padding: 200, duration: 1000, }, ]); const center = [ lotBbox[0] + (lotBbox[2] - lotBbox[0]) / 2, lotBbox[1] + (lotBbox[3] - lotBbox[1]) / 2, ]; const popupHtml = ` <strong>Property Details</strong> <table style="margin-top: 8px; border-collapse: collapse;"> <tr><td style="padding: 4px; font-weight: bold;">Lot</td><td style="padding: 4px;">${ lotnumber ?? "-" }</td></tr> <tr><td style="padding: 4px; font-weight: bold;">Section</td><td style="padding: 4px;">${ sectionnumber ?? "-" }</td></tr> <tr><td style="padding: 4px; font-weight: bold;">Plan</td><td style="padding: 4px;">${ plannumber ?? "-" }</td></tr> <tr><td style="padding: 4px; font-weight: bold;">Area</td><td style="padding: 4px;">${ planlotarea ?? "-" } m²</td></tr> </table> </div> `; await rpc.invoke("addHtmlPopup", [popupHtml, center, {}]); };Saving app configuration to Giraffe storage (via iFrame-SDK-react)
The app currently has hardcoded html content such as the text within the accordion card and this requires the app developer to change it. Giraffe has a json configuration editor which can be used to store the content and make elements of the app configurable. This is the location we configured the iFrame to point to your development server.
A json configuration structure can setup for your app and it will be saved with the Giraffe project.
I am going to make the accordions responsive to configuration - such as the number of cards, the title and content of the cards, as well as control the size of the app panel. So I will initially setup a json structure here with the content setup:
We can leverage the iFrame-SDK-react hook for this and call the configuration via selectedProjectApp.
The content will also include some HTML so we will purify this to avoid injection issues using the DOMpurify library.
export function CategoryAccordion() { const [openItem, setOpenItem] = useState<string | null>(null); const accordionItems = useGiraffeState("selectedProjectApp")?.public?.accordion ?? []; // Sort glossary keys alphabetically and sanitize content const processedItems = accordionItems.map(item => { // Sort glossary if it exists const sortedGlossary = item.glossary && Array.isArray(item.glossary) ? [...item.glossary].sort((a, b) => a.key.localeCompare(b.key)) : null; // Linkify and sanitize content const linkified = linkifyHtmlPreserveTags(item.content || ""); const sanitizedContent = DOMPurify.sanitize(linkified, { ADD_TAGS: ["iframe", "a"], ADD_ATTR: [ "allow", "allowfullscreen", "frameborder", "scrolling", "src", "title", "referrerpolicy", "target", "rel", "href", ], }); return { ...item, sanitizedContent, glossary: sortedGlossary, }; });And my Accordion is now configured to be dynamic
return ( <Accordion type="single" collapsible value={openItem ?? undefined} onValueChange={handleAccordionChange} className="w-full max-w-screen-lg mx-auto text-left" > {processedItems.map(({ title, sanitizedContent, glossary }) => ( <AccordionItem key={title} value={title}> <AccordionTrigger className="text-left border-b-4 border-blue-500"> {title} </AccordionTrigger> <AccordionContent className="prose max-w-screen-md body_font mx-auto mb-4 text-left"> {Array.isArray(glossary) && glossary.length > 0 ? ( <table className="w-full text-sm border border-white-100 mt-2"> <tbody> {glossary.map(({ key, value }, i) => ( <tr key={i} className="border-t border-white-100"> <td className="body_font text-gray-700 py-1 px-2 w-1/4 font-semibold"> {key} </td> <td className="body_font">{value}</td> </tr> ))} </tbody> </table> ) : ( <div className="body-font" dangerouslySetInnerHTML={{ __html: sanitizedContent }} /> )} </AccordionContent> </AccordionItem> ))} </Accordion> );
Conclusion
Now you have seen some examples you can leverage more of our SDK to achieve your own app
Remember the final app template is available within the public repository here
Good luck creating your own app!











