Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2fbac37
Add example for enforcing daily sync with LOADRECORDS
mmeesseman Mar 19, 2026
eb523ee
Add example for preventing duplicate repeatables
mmeesseman Mar 19, 2026
9875393
Add documentation for displaying field value labels
mmeesseman Mar 19, 2026
a7d6b32
Add example for visualizing data with Chart.js
mmeesseman Mar 19, 2026
abba8fc
Add 7 new examples from SE team code share audit
Mar 19, 2026
3348331
Add 12 new developer documentation examples from #se_team_code_share …
Mar 23, 2026
19cc34a
Add 4 more examples from attachment-only #se_team_code_share posts
Mar 23, 2026
284143c
Add medium-priority docs: Query API, Data Events, and Utilities examples
Mar 23, 2026
9578f74
Add 7 docs from previously unread LOW PRIORITY threads
Mar 24, 2026
d605c4d
Fix review file: remove 8 documented items from LOW PRIORITY table
Mar 24, 2026
f93351a
Add 10 docs from user-requested LOW PRIORITY threads
Mar 24, 2026
c9d65b7
Delete docs/App Extensions/app-extension-examples/add-text-caption-to…
mmeesseman Sep 29, 2026
422a307
Apply suggestion from @Copilot
mmeesseman Sep 29, 2026
b7de2bf
Apply suggestion from @Copilot
mmeesseman Sep 29, 2026
60fdc2c
Add origin and payload validation for postMessage
mmeesseman Sep 29, 2026
7bd378a
Prevent memory leaks by destroying existing chart instance
mmeesseman Sep 29, 2026
093e27f
Apply suggestion from @Copilot
mmeesseman Sep 29, 2026
cd5f221
Remove credit line from auto-populate example
mmeesseman Sep 29, 2026
893923c
Delete docs/DATA EVENTS/data-events-examples/calculate-status-from-re…
mmeesseman Sep 29, 2026
179133c
Remove credit line from data events documentation
mmeesseman Sep 29, 2026
98f1b9b
Apply suggestion from @Copilot
mmeesseman Sep 29, 2026
857c064
Improve task date caching and validation logic
mmeesseman Sep 29, 2026
7f52540
Remove include_geometry from LOADRECORDS
mmeesseman Sep 29, 2026
f4a2d09
Delete docs/DATA EVENTS/data-events-examples/prevent-creating-duplica…
mmeesseman Oct 2, 2026
9919a97
Apply suggestion from @Copilot
mmeesseman Oct 2, 2026
c323ffd
Remove credit line from documentation
mmeesseman Oct 2, 2026
22a84eb
Delete docs/DATA EVENTS/data-events-examples/user-action-logger.md
mmeesseman Oct 2, 2026
291a0fe
Delete docs/integrations/integration-examples/bamboohr-sync-worked-ho…
mmeesseman Oct 2, 2026
cb49969
Delete docs/integrations/integration-examples/bulk-update-app-setting…
mmeesseman Oct 2, 2026
2107c8d
Delete docs/integrations/integration-examples/identify-managed-vs-non…
mmeesseman Oct 2, 2026
82153d9
Delete docs/integrations/integration-examples/salesforce-create-fulcr…
mmeesseman Oct 2, 2026
efdc7bb
Delete docs/integrations/integration-examples/sequential-numbering-wi…
mmeesseman Oct 2, 2026
1acbe6f
Delete docs/integrations/integration-examples/sort-repeatable-rows-by…
mmeesseman Oct 2, 2026
363e6b1
Delete docs/Map Layers/map-layers-examples/convert-raster-and-vector-…
mmeesseman Oct 2, 2026
55ec138
Remove inactive users query from documentation
mmeesseman Oct 2, 2026
4d9ac58
Fix link format in concat-pdf-attachments-to-report.md
mmeesseman Oct 2, 2026
e37dadf
Remove credit line from concat PDF report example
mmeesseman Oct 2, 2026
ae1484e
Refactor getValue function for clarity
mmeesseman Oct 2, 2026
6ed59e9
Fix JSON stringification in attendance report
mmeesseman Oct 2, 2026
d3e5367
Remove credit line from dynamic reports example
mmeesseman Oct 2, 2026
365971f
Delete docs/slack-posts-for-review.md
mmeesseman Oct 2, 2026
0f4e130
Delete docs/Utilities/utilities-examples/visualize-record-link-networ…
mmeesseman Oct 2, 2026
324708c
Delete docs/Utilities/utilities-examples/print-record-link-dependenci…
mmeesseman Oct 2, 2026
6e2485c
Delete docs/Utilities/utilities-examples/export-import-issues-to-csv.md
mmeesseman Oct 2, 2026
1ed4b69
Remove App Designer debug mode instructions
mmeesseman Oct 2, 2026
2483ba9
Delete docs/Utilities/utilities-examples/api-token-copy-bookmarklet.md
mmeesseman Oct 2, 2026
bc565aa
Delete docs/REPORT BUILDER/reports-examples/simple-react-app-with-que…
mmeesseman Oct 2, 2026
049c635
Delete docs/REPORT BUILDER/reports-examples/recursive-photo-renderer.md
mmeesseman Oct 2, 2026
586a7ef
Update query-repeatables-photos-and-google-street-view.md
mmeesseman Oct 2, 2026
20f1238
Apply suggestion from @Copilot
mmeesseman Oct 2, 2026
874f464
Remove credit line from Puppeteer stall example
mmeesseman Oct 2, 2026
157c48d
Apply suggestion from @Copilot
mmeesseman Oct 2, 2026
7bca39d
Remove credit line from photo metadata report
mmeesseman Oct 2, 2026
00b7d8d
Delete docs/REPORT BUILDER/reports-examples/interactive-web-map-with-…
mmeesseman Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
---
title: Date picker with blackout dates
excerpt: >-
This App Extension opens a popup calendar that prevents users from selecting
blacked-out date ranges. Blackout dates are loaded dynamically from a
separate Fulcrum app using LOADRECORDS, so administrators can manage blocked
dates without touching the data event code.
deprecated: false
hidden: false
metadata:
title: ''
description: ''
robots: noindex
next:
description: ''
---

# Date picker with blackout dates

This example uses an App Extension to display a Flatpickr-powered calendar popup that restricts users from selecting certain dates. Blackout date ranges are stored in a separate Fulcrum app and loaded at runtime via `LOADRECORDS`, so administrators can add or remove blocked dates without updating the Data Event.

When the user clicks a button on the form, `OPENEXTENSION` launches the calendar. The selected date is returned to the form and written to a date field.

## How it works

1. A separate **blackout dates app** stores date range records (start date / end date).
2. On `load-record`, the Data Event fetches those ranges via `LOADRECORDS` and stores them in a variable.
3. When the user clicks the calendar button, `OPENEXTENSION` opens `calendar_picker.html` — a self-contained HTML page that uses [Flatpickr](https://flatpickr.js.org/) to render the calendar.
4. The blackout ranges are passed to the HTML page via the `data` option. Flatpickr disables those date ranges in the calendar.
5. When the user picks a date, the HTML page sends it back via `Fulcrum.finish()`, and the Data Event writes it to an appointment date field.

## Setup

1. Create a **blackout dates app** with two Date fields:
- `start` — the first day of the blocked range
- `end` — the last day of the blocked range (inclusive)
Note the **App ID** and the field keys for `start` and `end`.
2. Upload `calendar_picker.html` (below) as a **Reference File** in your Fulcrum org, or attach it directly to your app.
3. In your data collection app, add:
- A **Date** field for the appointment result (e.g. data name: `appointment_date`)
- A **Button** field to trigger the calendar (e.g. data name: `open_calendar`)
4. Add the Data Event code below to the app and update the configuration constants.

> **Note:** The Flatpickr calendar requires an internet connection when using the CDN version below. For fully offline use, replace the CDN links with a locally hosted or embedded copy of Flatpickr.

## Data Event Code

```js
// ─── Configuration ───────────────────────────────────────────────────────────

// App ID of the blackout dates app
const BLACKOUT_FORM_ID = 'YOUR-BLACKOUT-DATES-APP-ID-HERE';

// Field key of the start date field in the blackout app
const START_DATE_KEY = 'YOUR-START-DATE-FIELD-KEY';

// Field key of the end date field in the blackout app
const END_DATE_KEY = 'YOUR-END-DATE-FIELD-KEY';

// Data name of the date field to write the selected appointment date to
const APPOINTMENT_FIELD = 'appointment_date';

// Data name of the button field that opens the calendar
const CALENDAR_BUTTON = 'open_calendar';

// ─── Load blackout ranges on record open ─────────────────────────────────────

let blackoutRanges = [];

ON('load-record', () => {
LOADRECORDS({ form_id: BLACKOUT_FORM_ID }, (err, result) => {
if (err) {
console.log('Error loading blackout dates:', INSPECT(err));
return;
}

// Build an array of { start, end } objects from the loaded records
blackoutRanges = (result.records || []).map(rec => ({
start: rec.form_values[START_DATE_KEY] || '',
end: rec.form_values[END_DATE_KEY] || ''
})).filter(range => range.start && range.end);
});
});

// ─── Open the calendar extension ─────────────────────────────────────────────

ON('click', CALENDAR_BUTTON, () => {
if (!blackoutRanges.length) {
// Allow the calendar to open even if no blackout dates loaded yet
console.log('No blackout ranges loaded; opening calendar without restrictions.');
}

OPENEXTENSION({
url: 'attachment://calendar_picker.html',
title: 'Select Appointment Date',
width: 400,
height: 500,
data: {
blackoutRanges: blackoutRanges,
today: (function (d) {
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return year + '-' + month + '-' + day;
})(new Date())
},
onMessage: ({ data }) => {
if (data.selectedDate) {
SETVALUE(APPOINTMENT_FIELD, data.selectedDate);
}
}
});
});
```

## HTML Extension File (`calendar_picker.html`)

Save the content below as `calendar_picker.html` and attach it to your Fulcrum app as a Reference File. This file is loaded inside the `OPENEXTENSION` popup.

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Appointment Calendar</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
text-align: center;
padding: 20px;
background-color: #f9f9f9;
}
h2 { margin-bottom: 10px; }
#calendar { margin: 0 auto; max-width: 340px; }
.flatpickr-calendar { margin: 0 auto; font-size: 16px !important; }
button {
display: inline-block;
padding: 10px 18px;
margin-top: 20px;
font-size: 16px;
border-radius: 6px;
border: none;
background: #007aff;
color: white;
cursor: pointer;
}
button:hover { background: #005fcc; }
</style>

<!-- Flatpickr date picker library (requires internet connection) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>

<!-- Fulcrum App Extension communication bridge -->
<script>
(()=>{var s=(e,i)=>()=>(i||e((i={exports:{}}).exports,i),i.exports);
var o=s((a,r)=>{var l=new URLSearchParams(location.search);
function c(e){try{return JSON.parse(e)}catch(i){return null}}
r.exports=window.Fulcrum={isExtension:l.get("extension")==="1",
initialize:()=>{var i;let{params:e}=Fulcrum;Fulcrum.id=e?.id,Fulcrum.url=e?.url,Fulcrum.data=e?.data,Fulcrum.origin=e?.origin,(i=Fulcrum.onLoadOnce)?.call(Fulcrum)},
load:e=>{Fulcrum.onLoadOnce=()=>{Fulcrum.params&&!Fulcrum.isLoaded&&(Fulcrum.isLoaded=!0,e({data:Fulcrum.data}))},Fulcrum.onLoadOnce()},
send:(e,{close:i=!1}={})=>{var u;e=e||{};let n={id:Fulcrum.id,url:Fulcrum.url,data:e,close:i};
(u=window.webkit)?.messageHandlers?window.webkit.messageHandlers.extensionListener.postMessage(JSON.stringify(n)):
window.parent&&window.parent.postMessage({extensionMessage:n},Fulcrum.origin)},
receive:e=>{let i=c(e.data);i&&i.command==="initialize"&&!Fulcrum.params&&(Fulcrum.params=i.params,Fulcrum.initialize())},
finish:e=>{Fulcrum.send(e,{close:!0})}};Fulcrum.isExtension?
window.addEventListener("message",Fulcrum.receive,!1):window.addEventListener("DOMContentLoaded",Fulcrum.initialize)});o();})();
</script>
</head>

<body>
<h2>Select Appointment Date</h2>
<div id="calendar"></div>
<button id="cancel">Cancel</button>

<script>
Fulcrum.load(({ data }) => {
const blackoutRanges = data.blackoutRanges || [];

// Convert blackout ranges to the format Flatpickr expects for disabled dates
// Each range disables all dates from start through end (inclusive)
const disabledDates = blackoutRanges.map(r => {
const start = new Date(r.start);
const end = new Date(r.end);

start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
end.setDate(end.getDate() + 1); // include the full end day
Comment thread
mmeesseman marked this conversation as resolved.

return { from: start, to: end };
});

flatpickr('#calendar', {
inline: true,
minDate: data.today, // Prevent selecting dates in the past
disable: disabledDates, // Grey out the blackout date ranges
dateFormat: 'Y-m-d', // ISO format compatible with Fulcrum date fields
onChange: (selectedDates) => {
if (selectedDates[0]) {
const selected = selectedDates[0].toISOString().split('T')[0];
// Send the selected date back to the Data Event and close the extension
Fulcrum.finish({ selectedDate: selected });
}
}
});

// Cancel button closes the extension without setting a date
document.getElementById('cancel').addEventListener('click', () => {
Fulcrum.finish({});
});
});
</script>
</body>
</html>
```

## Notes

- The blackout date ranges are fetched each time the record is opened. If the blackout app is updated while a user has the form open, they will need to close and reopen the record to get the latest dates.
- Flatpickr's `disable` option accepts an array of `{ from, to }` objects. Dates within those ranges are greyed out and unselectable in the calendar UI.
- The `minDate: data.today` setting prevents users from selecting any date in the past.
- The `Fulcrum.finish({ selectedDate })` call sends the result back to the Data Event's `onMessage` handler and closes the extension popup.
Loading
Loading