Skip to content

Add the search page

The search page is an ordinary Fluid component with a search box, a summary of how the question was understood, and the results. All of its work happens in PeopleCode on the application server, through the search application class, so the browser never talks to the NL Search Service.

Definition What it’s for
ZZ_NLS_WRK Derived record: the question, the stored plan, the summary label, the count
ZZ_NLS_FILTER Derived record: one row per part of the summary, with a remove button
ZZ_NLS_RESULT Derived record: one row per result, with its title, summary, and link
ZZ_NL_SEARCH The Fluid page, and the component that holds it
ZZ_NLSEARCH The menu the component is registered on
  1. Create the derived records.

    • ZZ_NLS_WRK, with ZZ_QUESTION, a 254-character field; ZZ_SEARCH, for the Search button; ZZ_LABEL and ZZ_HITS, to show the summary label and the result count; and ZZ_PLAN, a long character field that holds the service’s response between requests.
    • ZZ_NLS_FILTER, with ZZ_NUM, ZZ_TEXT, and ZZ_ACTIVE, and ZZ_REMOVE, for its remove button.
    • ZZ_NLS_RESULT, with ZZ_TITLE, ZZ_SUMMARY, and ZZ_URL, and ZZ_OPEN, for its open button.
  2. Create the Fluid page, ZZ_NL_SEARCH. At level 0, put the search box, ZZ_QUESTION, with the Search button beside it, and then ZZ_LABEL and ZZ_HITS. Below them, add two grids: one for ZZ_NLS_FILTER, laid out as a row of filters, each with its remove button, and one for ZZ_NLS_RESULT, as a list of results. Keep ZZ_PLAN, ZZ_NUM, ZZ_ACTIVE, and ZZ_URL hidden.

  3. Create the component. Add the page to a new component, ZZ_NL_SEARCH. On the Fluid tab of the component properties, select Fluid Mode, and use INSTALLATION as the search record, since the component has no keys.

  4. Add the PeopleCode below.

One function runs the stored plan, leaving out the filters the user removed, and fills the results grid. Save it as FieldFormula PeopleCode on ZZ_NLS_WRK.ZZ_HITS, so that each button can call it:

ZZ_NLS_WRK.ZZ_HITS.FieldFormula
import ZZ_NLSEARCH:Search;
import PT_SEARCH:*;
Function ZZ_NLS_Show()
Local JsonObject &plan;
Local JsonParser &parser = CreateJsonParser();
If All(ZZ_NLS_WRK.ZZ_PLAN) Then
If &parser.Parse(ZZ_NLS_WRK.ZZ_PLAN) Then
&plan = &parser.GetRootObject();
End-If;
End-If;
/* The filters the user removed from the summary */
Local Rowset &filters, &grid;
Local array of number &removed = CreateArrayRept(0, 0);
Local integer &i;
&filters = GetLevel0()(1).GetRowset(Scroll.ZZ_NLS_FILTER);
For &i = 1 To &filters.ActiveRowCount
If &filters(&i).ZZ_NLS_FILTER.ZZ_ACTIVE.Value = "N" Then
&removed.Push(&filters(&i).ZZ_NLS_FILTER.ZZ_NUM.Value);
End-If;
End-For;
/* Run the search, and fill the results grid */
Local ZZ_NLSEARCH:Search &nls = create ZZ_NLSEARCH:Search();
Local PT_SEARCH:SearchResultCollection &results;
Local PT_SEARCH:SearchResult &result;
Local Record &rec;
Local string &q = ZZ_NLS_WRK.ZZ_QUESTION;
&results = &nls.Run(&plan, &q, &removed, 1, 50);
&grid = GetLevel0()(1).GetRowset(Scroll.ZZ_NLS_RESULT);
&grid.Flush();
For &i = 1 To &results.GetDocumentCount()
If &i > 1 Then
&grid.InsertRow(&i - 1);
End-If;
&result = &results.Item(&i);
&rec = &grid(&i).ZZ_NLS_RESULT;
&rec.ZZ_TITLE.Value = &result.GetTitle();
&rec.ZZ_SUMMARY.Value = &result.GetSummary();
&rec.ZZ_URL.Value = &result.GetUrlLink();
End-For;
ZZ_NLS_WRK.ZZ_HITS = &results.GetEstimatedHitCount();
End-Function;

The Search button asks for a plan, keeps it, fills the summary with one removable filter for each of its parts, and shows the results:

ZZ_NLS_WRK.ZZ_SEARCH.FieldChange
import ZZ_NLSEARCH:Search;
Declare Function ZZ_NLS_Show PeopleCode
ZZ_NLS_WRK.ZZ_HITS FieldFormula;
Local ZZ_NLSEARCH:Search &nls = create ZZ_NLSEARCH:Search();
Local JsonObject &plan = &nls.Plan(ZZ_NLS_WRK.ZZ_QUESTION);
Local JsonObject &summary;
Local JsonArray &parts;
Local Rowset &filters;
Local Record &rec;
Local integer &i;
/* Keep the plan, so removing a filter needs no new plan */
ZZ_NLS_WRK.ZZ_PLAN = "";
ZZ_NLS_WRK.ZZ_LABEL = "";
&filters = GetLevel0()(1).GetRowset(Scroll.ZZ_NLS_FILTER);
&filters.Flush();
If &plan <> Null Then
ZZ_NLS_WRK.ZZ_PLAN = &plan.ToString();
/* Log the request ID with %UserId here, in ZZ_NLS_LOG */
If Not &plan.IsNullProperty("intent") Then
&summary = &plan.GetJsonObject("summary");
&parts = &summary.GetJsonArray("filters");
ZZ_NLS_WRK.ZZ_LABEL = &summary.GetString("label");
For &i = 1 To &parts.Length()
If &i > 1 Then
&filters.InsertRow(&i - 1);
End-If;
&rec = &filters(&i).ZZ_NLS_FILTER;
&rec.ZZ_NUM.Value = &i;
&rec.ZZ_TEXT.Value = &parts.GetString(&i);
&rec.ZZ_ACTIVE.Value = "Y";
End-For;
End-If;
End-If;
ZZ_NLS_Show();

When there’s no plan, the summary stays empty, and ZZ_NLS_Show runs a keyword search with the question as typed.

The remove button hides its filter, and shows the results again from the stored plan, without asking the service for a new one:

ZZ_NLS_FILTER.ZZ_REMOVE.FieldChange
Declare Function ZZ_NLS_Show PeopleCode
ZZ_NLS_WRK.ZZ_HITS FieldFormula;
GetRow().ZZ_NLS_FILTER.ZZ_ACTIVE.Value = "N";
GetRow().Visible = False;
ZZ_NLS_Show();

The open button opens the result’s page:

ZZ_NLS_RESULT.ZZ_OPEN.FieldChange
/* Open the result; its page checks the user's access again */
ViewURL(GetRow().ZZ_NLS_RESULT.ZZ_URL.Value);
  1. Register the component. With the component open in Application Designer, choose Register Component to start the registration wizard. Add it to the ZZ_NLSEARCH menu, create its content reference in the Fluid Structure Content folder, and add it to the permission list ZZ_NLS_USER.

  2. Create a tile. At PeopleTools > Portal > Tile Wizard, create a tile for the component, named Search in plain language, and add it to the homepages of the people who search vouchers, and to the NavBar.

Sign in as a test user with ZZ_NLS_USER, open the tile, and search for “Vouchers over $10,000 from Acme entered last month”. The summary reads Vouchers · Supplier: Acme · Amount: over 10,000 · Entered: 1–31 August 2026, and the results are Acme’s vouchers from the test user’s business units only. Remove the amount filter, and the results widen to all of Acme’s vouchers from last month in those business units, straight away, with no new call to the service.