Custom script search events let you hook into the search webpart lifecycle and react to user actions.
All examples below assume you have access to the global tisa object (Search Script API).
type onInitComplete = () => Promise<void>;
async function onInitComplete() {
// The Search Script API is fully initialized at this point.
// Use it for setting default field values, controlling visibility, permission checks.
tisa.search.field.s_invoiceNumber.value = "INV-001";
tisa.search.field.s_supplierName.hidden = true;
const membership = await tisa.user.isMemberOfGroup({
groups: ["Approvers"],
});
tisa.log.info("Is approver: " + membership["Approvers"].isMember);
}
This event runs after the Search Script API is initialized and before the form is interactive. Use it for async setup, permission checks, or setting initial field values.
type onSearchComplete = (
results: SearchResultItem[],
triggerName: string,
) => Promise<void>;
async function onSearchComplete(results, triggerName) {
// results = array of SharePoint items from all source lists,
// each enriched with listId (GUID) and listUrl (server-relative)
// triggerName = what triggered the search:
// "search" — standard Search button
// "code" — tisa.search.submit() without explicit name
// "<custom>" — tisa.search.submit("QuickSearch") or any custom string
tisa.log.info(
"Found " + results.length + " items (trigger: " + triggerName + ")",
);
if (triggerName === "QuickSearch" && results.length === 1) {
window.open(results[0].FileRef, "_blank");
return;
}
// Group results by source list
const byList = results.reduce((acc, item) => {
(acc[item.listId] ??= []).push(item);
return acc;
}, {});
if (results.length === 0) {
tisa.search.setTab("Invoice_Advanced");
}
}
Fires always after each search completes — including when the search returns zero results or encounters an error (5000 threshold, network error). In error cases, results is an empty array.
results — SearchResultItem[] from all source lists, each item enriched with:
listId — GUID of the source SharePoint listlistUrl — server-relative URL of the source list's root folder (e.g. "/sites/MySite/Lists/Invoices")triggerName — identifies the source of the searchtype onTabChange = (tabName: string) => void;
function onTabChange(tabName) {
// tabName = internal group name (translation key, e.g. "Invoice_Advanced")
tisa.log.debug("Switched to tab: " + tabName);
if (tabName === "Invoice_Advanced") {
tisa.search.field.s_dateFrom.value = null;
}
}
Fires synchronously whenever the active tab changes (both user click and programmatic setTab()). The tabName parameter is the internal group name (translation key, with the RESX. prefix stripped). It matches the value of tisa.search.activeTab.
Note:
onTabChangeis a synchronous event. For async operations triggered by tab changes, start your async work inside the handler but do not return a promise.
The tisa.search object exposes operations on the search data and form.
await tisa.search.submit("QuickSearch");
Programmatically triggers the search using current form field values. The optional triggerName is forwarded to onSearchComplete as the second argument.
undefined → triggerName = "code" in onSearchCompletetriggerName = "search"async function btnQuickSearchCommand() {
tisa.search.field.s_status.value = "Active";
tisa.search.field.s_category.value = "Urgent";
await tisa.search.submit("QuickSearch");
}
tisa.search.reset();
Resets the search form to its default state (clears all field values). Equivalent to clicking the Reset button.
async function btnQuickSearchCommand() {
tisa.search.reset();
tisa.search.field.s_status.value = "Active";
await tisa.search.submit("QuickSearch");
}
tisa.search.exportCSV();
tisa.search.exportXLS();
Triggers export of the current search results (sorted/filtered as in the table) to CSV or Excel.
tisa.search.setTab("Invoice_Advanced");
Switches the active tab by its internal group name (translation key).
tisa.search.field.s_invoiceNumber.value = "INV-001";
tisa.search.field.s_supplierName.hidden = true;
tisa.search.field.s_status.readOnly = true;
tisa.search.field.s_invoiceNumber.required = true;
tisa.search.field.s_category.onChange((newValue, { oldValue }) => {
console.log(oldValue + " -> " + newValue);
});
Dictionary of search form fields for the active tab. Each field provides get/set for value, hidden, readOnly, required, and an onChange subscription.
const items = tisa.search.results;
// Each item has listId and listUrl from the source list
items?.forEach((item) => {
console.log(item.Id, item.listId, item.listUrl);
});
Last search result set (SearchResultItem[]) from all source lists. Each item includes listId (GUID) and listUrl (server-relative URL) identifying the source list. Returns undefined when no search has been performed yet.
const tab = tisa.search.activeTab;
Internal group name (translation key) of the currently active tab. Matches the group Name from the JSON configuration (with RESX. prefix stripped).
const searching = tisa.search.isSearching;
Whether a search is currently in progress. Useful in EnabledFunction to disable buttons during search.
Action buttons can reference three types of custom script functions via their JSON configuration:
CommandFunction, EnabledFunction, and VisibleFunction.
All three are looked up by name in the custom script's function namespace.
Called (async) when the user clicks the button.
type CommandFunction = () => Promise<void>;
async function btnStartWfCommand() {
const results = tisa.search.results;
const { translation } = tisa.utils;
const confirmed = await tisa.utils.addDialog({
Title: translation("Action.StartWf.Confirm.Title"),
Template: translation("Action.StartWf.Confirm.Body", {
count: results.length,
}),
});
if (confirmed.button !== "submit") return;
const items = results.map((item) => ({
listId: item.listId,
itemId: item.Id,
}));
await tisa.utils.runWithLoader(async () => {
const responses = await tisa.workflow.startOnItems(
"ApprovalWorkflow",
items,
);
const failed = responses.filter((r) => r.status !== "Ok");
if (failed.length > 0) {
tisa.log.warn("Failed to start WF on " + failed.length + " items", {
showToast: true,
});
} else {
tisa.log.info("Workflow started on " + results.length + " items", {
showToast: true,
});
}
});
}
A synchronous function that returns boolean. It is re-evaluated whenever the active tab changes or search completes.
If EnabledFunction is not set, the button is always enabled.
type EnabledFunction = () => boolean;
function btnStartWfEnabled() {
return (
isApprover &&
!tisa.search.isSearching &&
tisa.search.results &&
tisa.search.results.length > 0
);
}
A synchronous function that returns boolean. It is re-evaluated whenever the active tab changes or search completes.
If VisibleFunction is not set, the button is always visible.
type VisibleFunction = () => boolean;
function btnQuickSearchVisible() {
return tisa.search.activeTab === "Invoice_Basic";
}
Below is a complete example showing the JSON configuration together with the matching custom script.
{
"Title": "Invoice_Search",
"CustomScript": "...(see script below)...",
"ActionButtons": [
{
"Title": "Action.StartWf",
"ID": "StartWf",
"CommandFunction": "btnStartWfCommand",
"EnabledFunction": "btnStartWfEnabled",
"Icon": { "Icon": "regular/play" }
}
],
"Groups": [
{
"Name": "Invoice_Basic",
"ActionButtons": [
{
"Title": "Action.QuickSearch",
"ID": "QuickSearch",
"CommandFunction": "btnQuickSearchCommand",
"Icon": { "Icon": "regular/bolt" }
}
],
"Source": {
"Type": "SP",
"SearchScope": "SourceList",
"SourceList": ["{SiteUrl}/InboundInvoices"]
},
"FormRows": [
{
"Columns": [
{
"Fields": [
{ "InternalName": "s_status" },
{ "InternalName": "s_supplierName" }
]
}
]
}
],
"Columns": [
{ "InternalName": "s_invoiceNumber", "DisplayLink": true }
]
},
{
"Name": "Invoice_Advanced",
"Source": {
"Type": "SP",
"SearchScope": "SourceList",
"SourceList": [
"{SiteUrl}/InboundInvoices",
"{SiteUrl}/InboundInvoicesArchive"
]
},
"FormRows": [
{
"Columns": [
{
"Fields": [
{ "InternalName": "s_invoiceNumber" },
{ "InternalName": "s_status" },
{ "InternalName": "s_dateFrom" }
]
}
]
}
],
"Columns": [
{ "InternalName": "s_invoiceNumber", "DisplayLink": true }
]
}
],
"Resources": [
{
"LCID": "1029",
"Invoice_Basic": "Základní",
"Invoice_Advanced": "Pokročilé",
"Action": {
"StartWf": "Spustit schválení",
"StartWf.Confirm": {
"Title": "Potvrzení",
"Body": "Opravdu spustit workflow pro {{count}} položek?"
},
"QuickSearch": "Rychlé hledání"
}
},
{
"LCID": "1033",
"Invoice_Basic": "Basic",
"Invoice_Advanced": "Advanced",
"Action": {
"StartWf": "Start Approval",
"StartWf.Confirm": {
"Title": "Confirmation",
"Body": "Start workflow for {{count}} items?"
},
"QuickSearch": "Quick Search"
}
}
]
}
// --- state ---
let isApprover = false;
// --- lifecycle ---
async function onInitComplete() {
const membership = await tisa.user.isMemberOfGroup({
groups: ["Approvers"],
});
isApprover = membership["Approvers"].isMember;
tisa.log.debug("Init complete, isApprover=" + isApprover);
}
async function onSearchComplete(results, triggerName) {
tisa.log.info(triggerName + ": " + results.length + " results");
if (triggerName === "QuickSearch" && results.length === 1) {
window.open(results[0].FileRef, "_blank");
}
}
function onTabChange(tabName) {
tisa.log.debug("Switched to: " + tabName);
}
// --- Start Workflow button (global) ---
function btnStartWfEnabled() {
return (
isApprover &&
!tisa.search.isSearching &&
tisa.search.results &&
tisa.search.results.length > 0
);
}
async function btnStartWfCommand() {
const results = tisa.search.results;
const { translation } = tisa.utils;
const confirmed = await tisa.utils.addDialog({
Title: translation("Action.StartWf.Confirm.Title"),
Template: translation("Action.StartWf.Confirm.Body", {
count: results.length,
}),
});
if (confirmed.button !== "submit") return;
const items = results.map((item) => ({
listId: item.listId,
itemId: item.Id,
}));
await tisa.utils.runWithLoader(async () => {
const responses = await tisa.workflow.startOnItems(
"ApprovalWorkflow",
items,
);
const failed = responses.filter((r) => r.status !== "Ok");
if (failed.length > 0) {
tisa.log.warn("Failed on " + failed.length + " items", {
showToast: true,
});
} else {
tisa.log.info("Workflow started on " + results.length + " items", {
showToast: true,
});
}
});
}
// --- Quick Search button (Basic tab only) ---
async function btnQuickSearchCommand() {
tisa.search.reset();
tisa.search.field.s_status.value = "Active";
await tisa.search.submit("QuickSearch");
}
Tip:
EnabledFunctionandVisibleFunctionare re-evaluated each time the active tab changes or search completes, so you can combine permission flags (set inonInitComplete) with search state to control button availability.