🎯 Project Overview
Build an interactive Suitelet that:
- Displays a form with saved search selector
- Shows a confirmation dialog before submission
- Triggers a Map/Reduce script via N/task
- Polls for task status using setInterval
- Displays real-time progress with messages
📋 Suitelet Form
function onRequest(context) {
if (context.request.method === 'GET') {
const form = serverWidget.createForm({ title: 'Run Report' });
// Saved search selector
form.addField({
id: 'custpage_search',
type: serverWidget.FieldType.SELECT,
label: 'Customer Search',
source: 'search'
});
// Status display (INLINEHTML for custom content)
form.addField({
id: 'custpage_status',
type: serverWidget.FieldType.INLINEHTML,
label: ' '
}).defaultValue = '<div id="statusArea"></div>';
form.addSubmitButton({ label: 'Run Report' });
form.clientScriptModulePath = './scheduler_cs.js';
context.response.writePage(form);
} else {
// Handle POST - schedule the task
const searchId = context.request.parameters.custpage_search;
const mrTask = task.create({
taskType: task.TaskType.MAP_REDUCE,
scriptId: 'customscript_csv_export_mr',
params: { custscript_customer_search: searchId }
});
const taskId = mrTask.submit();
// Return task ID for status polling
context.response.write(JSON.stringify({ taskId: taskId }));
}
}🎨 Client Script with Status Polling
function pageInit(context) {
// Attach to form submit
}
function saveRecord(context) {
// Confirm before submitting
return dialog.confirm({
title: 'Confirm',
message: 'Run the export now?'
}).then(result => {
if (result) {
submitAndPoll();
}
return false; // Prevent normal form submit
});
}
function submitAndPoll() {
const statusDiv = document.getElementById('statusArea');
// Show processing message
const msg = message.create({
title: 'Processing',
message: 'Report generation started...',
type: message.Type.INFORMATION
});
msg.show();
// Poll for status every 3 seconds
const interval = setInterval(() => {
fetch(suiteletUrl + '&action=status&taskId=' + taskId)
.then(response => response.json())
.then(data => {
if (data.status === 'COMPLETE') {
clearInterval(interval);
msg.hide();
message.create({
title: 'Complete',
message: 'Report generated successfully!',
type: message.Type.CONFIRMATION
}).show();
} else if (data.status === 'FAILED') {
clearInterval(interval);
msg.hide();
message.create({
title: 'Error',
message: 'Report generation failed.',
type: message.Type.ERROR
}).show();
}
});
}, 3000);
}📊 Status Check Endpoint
// In the Suitelet, handle status check action
if (context.request.parameters.action === 'status') {
const taskId = context.request.parameters.taskId;
const status = task.checkStatus({ taskId: taskId });
context.response.write(JSON.stringify({
status: status.status
}));
return;
}✅ Pattern: Action Routing
Use an "action" parameter to route different requests to the same Suitelet—default for form display, "schedule" for task creation, "status" for polling.
📖 Finding This in the Docs
This capstone combines multiple modules. Key references:
- Suitelet Script Type → onRequest entry point, context.request/response
- N/ui/serverWidget Module → form building
- N/task Module → task.create(), task.checkStatus()
- N/ui/message Module → banner notifications
Key pages to bookmark:
- Suitelet Script Type → handling GET vs POST requests
- serverWidget.FieldType.INLINEHTML → custom HTML in forms
- Form.clientScriptModulePath → attaching client scripts
- N/ui/dialog Module → dialog.confirm() for user prompts
- task.checkStatus() → polling task progress
🎯 Key Takeaways
- Combine serverWidget, N/task, and client scripts for interactive UIs
- Use INLINEHTML fields for custom JavaScript-driven content
- dialog.confirm() provides user confirmation before actions
- setInterval + fetch enables real-time status polling
- Action parameters route different requests to one Suitelet
- message.create() displays status banners to users