MultiTableSearch is a portal's global search: it searches several tables at once and lists what it finds by name — as cards, a list or a grid — each row with its table, its name and the columns its table's view shows, and what the search matched highlighted. Every table is read with a grid's security, a page at a time, so a table is read further only when the rows shown need it.
<MultiTableSearch /><MultiTableSearch />Type a name, an email address or a city: the demo's accounts, contacts and opportunities are searched together, and each name opens its record's page. Choose a layout, sort the rows by name or by when they were last changed, or group them by table from Settings, and switch tables on and off from Filter, which says how many of each table's rows were found. Signed in, change what each table shows and save views of your own.
MultiTableSearchBox is a search box for a page's header. As you type it suggests the first rows that match, each with its table, and picking one opens its record's page; See all results, first in the list, opens the page with the MultiTableSearch — here, the search above. With nothing typed, it offers your recent searches and the records you opened. The arrow keys move through the suggestions, Enter picks one and Escape closes them.
<MultiTableSearchBox searchPageUrl="search" onNavigate={navigate} /><MultiTableSearchBox SearchPageUrl="search" />SearchPageUrl (searchPageUrl) is the page with the MultiTableSearch, given what to search for as ?q=. Tables limits the tables searched, MaximumResults how many rows are suggested (6 unless set), and RecordUrl, OnSelect and ShowRecordLinks open a row as a MultiTableSearch's do. In React, give it an onNavigate so its pages open through your router.
Ctrl+K (⌘K on a Mac) anywhere on the page, and / where nothing is being typed, put the cursor in the box; KeyboardShortcut (keyboardShortcut) turns that off, as a page with two boxes does for one. With Collapsible (collapsible), a narrow screen shows the box as a search button until it is opened, and it folds away again once left empty.
<MultiTableSearchBox searchPageUrl="search" collapsible keyboardShortcut onNavigate={navigate} /><MultiTableSearchBox SearchPageUrl="search" Collapsible="true" KeyboardShortcut="true" />Register the tables to search, put a MultiTableSearch on a page of its own, and the box in the layout's header pointing at it. Each row then opens its record's page. The project templates come set up this way, for accounts and contacts.
// The tables searched, and the pages their rows open.
builder.Services.AddMultiTableSource("account", t => t.ForSearch());
builder.Services.AddMultiTableSource("contact", t => t.ForSearch());
builder.Services.AddRecordPage("account", "accounts/account?accountId={id}");
builder.Services.AddRecordPage("contact", "contacts/contact?contactId={id}");
// The Search page.
{ path: 'search', element: <MultiTableSearch allowSavedViews showTotals onNavigate={navigate} /> }
// In the layout's header.
<MultiTableSearchBox searchPageUrl="search" collapsible onNavigate={navigate} style={{ width: 'min(320px, 40vw)' }} />@* The Search page. *@
@attribute [Route("search")]
<MultiTableSearch AllowSavedViews="true" ShowTotals="true" />
@* In the layout's header. *@
<MultiTableSearchBox SearchPageUrl="search" Width="min(320px, 40vw)" Collapsible="true" />A search reads only the tables registered for it, with ForSearch. A table registered for searches alone stays out of timelines, calendars and maps, and needs no date; registering a table again adds to the controls it is for. Notes and activities are registered for timelines and calendars by default: to search one alone, remove it with RemoveMultiTableSource before registering it again.
builder.Services.AddMultiTableSource("account", t => t.ForSearch());
builder.Services.AddMultiTableSource("contact", t => t.ForSearch());
// Emails in searches too, as well as in timelines and calendars.
builder.Services.AddMultiTableSource("email", t => t.ForSearch());
Without a view of its own, a table is read through its Quick Find view: its rows titled by the table's primary name, showing the view's columns, and searched in its find columns — as Dynamics' own search reads it.
A view registered for search says, table by table, which rows are searched and how they show. Each table's FetchXML carries all of it.
builder.Services.AddMultiTableView(customersViewId, "Customers", v => v
.ForSearch()
.Table("account", t => t.FetchXml("""
<fetch><entity name="account">
<attribute name="name" /><attribute name="emailaddress1" /><attribute name="telephone1" />
<order attribute="name" />
<filter type="and">
<condition attribute="statecode" operator="eq" value="0" />
<filter type="or" isquickfindfields="1">
<condition attribute="name" operator="like" value="{0}" />
<condition attribute="emailaddress1" operator="like" value="{0}" />
</filter>
</filter>
</entity></fetch>
"""))
.Table("contact"));
Each row is named by its table's title — its primary name unless the registration sets Title. The query's first <order> is what the rows are listed in order of, and shows first among their columns where it isn't the title; its <attribute>s are the columns they show, a joined table's among them; its quick-find filter (isquickfindfields="1") names the columns a search looks in; and its other filters choose the rows. A table with no query is read through its Quick Find view, in order of its title.
The rows of several tables are listed in one order, so every table must be ordered by a column of the same type — text, a number or a date. A table ordered by a column of another type than the first table's is left out of the search and reported, and the rest are shown.
OrderBy (orderBy) sorts the rows of every table the same way, whatever its view says: by its title, by when the row was last changed (ModifiedOn) or by when it was made (CreatedOn); View keeps each view's own order. Either way the rows keep their names, and a date they are sorted by shows first among their columns. The user chooses the order from Settings, and a saved view keeps it.
<MultiTableSearch orderBy={MultiTablePageOrder.ModifiedOn} descending /><MultiTableSearch OrderBy="MultiTablePageOrder.ModifiedOn" Descending="true" />BestMatch, the default, puts the rows whose title matches the search first, by title — ascending, an exact title before the longer ones it begins — then every other row that matches, by title. The ranking holds across tables and pages: no other row shows while any table has a title match left to read. Set View to keep each view's own order instead.
<MultiTableSearch orderBy={MultiTablePageOrder.View} /><MultiTableSearch OrderBy="MultiTablePageOrder.View" /><MultiTableSearch defaultViewId={customersViewId} /><MultiTableSearch DefaultViewId="customersViewId" />A search works as a grid's does: a row matches when a search column starts with what was typed, or contains it after a leading *, ignoring case and accents. Lookups match by their name, choices by their label, and number and date columns by >, <, >= or <= before a number, a day or a year.
Each row shows which of its columns matched, highlighting what was typed; a search column that matched but isn't shown is listed under the others, so no row is found without a reason. With nothing typed, the search reads nothing.
Filter lists each table with how many of its rows were found, as do the headings of rows grouped by table: a table with more to read than it has shown says so — Accounts (25+) — and its count grows as more are read.
With ShowTotals (showTotals) each table is named with every row of it that matches — Accounts (1,254) — counted when the first page is read: one more query a table, run with the user's own access and split past Dataverse's limit of 50,000 rows an aggregate reads.
<MultiTableSearch showTotals /><MultiTableSearch ShowTotals="true" />The arrow keys move from the search box into the rows and between them, and Home and End go to the first and the last; Up from the first goes back to the box.
A screen reader hears how many rows were found once a search has read them, and that there are more to load.
Filter offers three filters of every table at once: My records, Active records, and when the rows were last changed — in the last 7 or 30 days, or the last year. MineOnly, ActiveOnly and ModifiedWithinDays (mineOnly, activeOnly, modifiedWithinDays) set them as the search opens, and a saved view keeps them.
<MultiTableSearch activeOnly modifiedWithinDays={30} /><MultiTableSearch ActiveOnly="true" ModifiedWithinDays="30" />A user's records are those table security records them as owning: the contact columns its RowsOwnedByContact and OwnedBy rules set when a row is created, party lists included. My records is offered to signed-in users, and a table that records no owner for them isn't searched while it is on — the search names it. Active records are those whose status is 0, of the tables that have one.
The search keeps what the user searched for and the records they opened from it, and with nothing typed offers them again, with a way to clear them. They are kept in the user's browser, each user's apart, and shared with a MultiTableSearchBox. ShowRecent (showRecent) turns them off.
Regarding (regarding) searches only the rows about one record, through each table's regarding column: notes' objectid, activities' regardingobjectid (with the activities the record takes part in), and for other tables the column their registration names with Regarding.
builder.Services.AddMultiTableSource("contact", t => t.Regarding("parentcustomerid").ForSearch());
builder.Services.AddMultiTableSource("opportunity", t => t.Regarding("customerid").ForSearch());
<MultiTableSearch regarding={account} /><MultiTableSearch Regarding="@account" />A table with no such column has nothing about the record to search, so it is left out and named above the results.
Notes are registered for timelines already. Register them for search too, naming the columns a search looks in, as notes have no Quick Find view — their file name among them, and a search finds attachments by name:
builder.Services.AddMultiTableSource("annotation", t => t
.SearchColumns("subject", "notetext", "filename")
.ForSearch());
A note's row is titled by its subject. Only the notes meant for the portal are read, as everywhere: those whose text starts with *WEB*. On a record's page, a search finds the notes about it.
Layout chooses cards, a list or a grid, which the user can change from the toolbar unless AllowLayoutChange is off. GroupByTable shows each table's rows under its name with a Show more of their own, and Descending lists the rows from last to first. PageSize is how many rows show at a time, and users choose another from Settings among PageSizes (pageSizes): 10, 25, 50 and 100 unless set, an empty list offering no choice. A saved view keeps the one chosen.
<MultiTableSearch layout={MultiTableSearchLayout.List} groupByTable pageSize={10} /><MultiTableSearch Layout="MultiTableSearchLayout.List" GroupByTable="true" PageSize="10" />ItemTemplate (renderItem in React) draws a row the page's own way.
A row's name links to its record's page, registered with AddRecordPage (see Record Pages), where its table has one. RecordUrl (recordUrl) makes it link elsewhere; OnSelect (onSelect) has the page handle the pick instead, for a row RecordUrl gives no link; and ShowRecordLinks (showRecordLinks) turns the record pages' links off.
<MultiTableSearch recordUrl={(item) => `/${item.table}/${item.id}`} /><MultiTableSearch RecordUrl="@(item => $"/{item.Table}/{item.Id}")" />In React, give the search an onNavigate, such as React Router's navigate, and a click on a link goes through your router; without one it is an ordinary link.
With AllowSavedViews, signed-in users save the tables shown, what each shows and how the search is laid out as views of their own, offered beside the page's, and choose their default and hidden views. They are kept as views of several tables that name searches, so the views saved on timelines and calendars aren't offered here, nor these there. SavedViewsKey keeps a search's views to itself.
Filter's Edit filters opens each table's query as a tab: the column its rows are sorted by, the columns they show, the columns a search looks in and the rows it reads.
From Manage views, a user can give the columns of a view of theirs titles of their own, as a grid's columns can be retitled.
<MultiTableSearch allowSavedViews savedViewsKey="site-search" /><MultiTableSearch AllowSavedViews="true" SavedViewsKey="site-search" />What is searched for is kept in the page's address as ?q=, so a link or a reload searches again, and a MultiTableSearchBox elsewhere on the site sends visitors to the page — the search follows the address when it changes under it. SearchParameterName names another parameter, or null keeps it out of the address.
IMultiTableQueryService.LoadPagesAsync (loadMultiTablePagesAsync in React) returns a page of each table in order, not merged, each with the paging cookie to read its next page from. MultiTablePageMerger merges them and says which tables to read for more.
Name | Type | Default | Description |
|---|---|---|---|
ActiveOnly | bool | False | Whether only active rows show at first: those whose status is 0, of the tables that have one. |
AllowLayoutChange | bool | True | Whether the user can choose the layout. |
AllowSavedViews | bool | False | Lets signed-in users change what each table shows, save views of their own — the tables, what each shows and is searched by, and the layout — offered in the view picker beside the page's, and choose their default view and hide the views they don't use. Off by default. Needs the Power Portals Pro Core solution's multi-table view tables; an internal user also needs the |
DefaultTables | IEnumerable<string>? | The tables searched at first; the user can switch on the others. Unset, every table. | |
DefaultViewId | Guid? | The view shown at first: one registered for search, by id. Unset, every table — or the user's default view. | |
Descending | bool | False | Whether the rows run from the last to the first at first. |
GroupByTable | bool | False | Whether the rows show by table at first, each table's under its name with more of its own to show. |
ItemTemplate | RenderFragment<MultiTablePageItem>? | A row drawn the page's own way, in place of the layout's. | |
Layout | MultiTableSearchLayout | Cards | How the rows are laid out at first. |
MineOnly | bool | False | Whether only the signed-in user's own rows show at first: those table security records them as owning ( |
ModifiedWithinDays | int? | Only the rows changed in this many days show at first: 1 to 3,650. Unset, whenever they were changed. | |
OrderBy | MultiTablePageOrder | BestMatch | What the rows are ordered by at first: the best matches first unless set ( MultiTablePageOrder.BestMatch). A column the rows are ordered by that isn't the title shows first among a row's columns. |
PageSize | int | 25 | How many rows show at a time, and are read of a table at a time: 1 to 100. |
PageSizes | IReadOnlyList<int> | The numbers of rows at a time the Settings menu offers, each 1 to 100, with MultiTableSearch.PageSize among them. Default 10, 25, 50 and 100; none offers no choice. | |
RecordUrl | Func<MultiTablePageItem, string>? | Where a row's name links to, in place of its record's page registered with null for no link. | |
Regarding | TableRecordReference? | Only the rows about this record, through each table's regarding column. | |
SavedViewsKey | string? | Names this search among the searches, so the views saved on it and the user's default and hidden views are its own. Without one, it shares its views with every search that has none. Letters, digits, | |
Search | string? | What to search for. With nothing, the search shows nothing until the user types. | |
SearchParameterName | string? | q | The page address's query parameter the search is kept in, so a link or a reload searches again: null keeps it out of the address. |
ShowRecent | bool | True | Whether the search keeps the user's searches and the records they open from it, and offers them while nothing is typed: in their browser, for each user apart, shared with a Components.MultiTableSearchBox. Default true. |
ShowRecordLinks | bool | True | Whether a row's name links to its record's page, registered with MultiTableSearch.RecordUrl nor MultiTableSearch.OnSelect is given. Default true; a table with no page shows no link. |
ShowTotals | bool | False | Whether each table's name shows how many of its rows match in all, counted with its first page — a query of each table more — rather than how many have been read so far. |
SignInUrl | string | Account/Login | Where a user who isn't signed in is sent to sign in, from the saved views' menu, which asks them to first; they come back to this page. Defaults to |
Tables | IEnumerable<string>? | The tables the search may read, by logical name. Unset, every table registered for search. | |
Title | string? | The heading shown where there is no view to choose. |
ActiveOnlyAllowLayoutChangeAllowSavedViewsDefaultTablesDefaultViewIdDescendingGroupByTableItemTemplateLayoutMineOnlyModifiedWithinDaysOrderByMultiTablePageOrder.BestMatch). A column the rows are ordered by that isn't the title shows first among a row's columns.PageSizePageSizesMultiTableSearch.PageSize among them. Default 10, 25, 50 and 100; none offers no choice.RecordUrlnull for no link.RegardingSavedViewsKeySearchSearchParameterNamenull keeps it out of the address.ShowRecentComponents.MultiTableSearchBox. Default true.ShowRecordLinksMultiTableSearch.RecordUrl nor MultiTableSearch.OnSelect is given. Default true; a table with no page shows no link.ShowTotalsSignInUrlTablesTitleName | Type | Description |
|---|---|---|
LayoutChanged | EventCallback<MultiTableSearchLayout> | Raised when the user chooses another layout. |
OnSelect | EventCallback<MultiTablePageItem> | Raised when the user picks a row that has no link of MultiTableSearch.RecordUrl's. A page that handles the picks is linked to no record pages. |
SearchChanged | EventCallback<string> | Raised as the user changes what is searched for. |
LayoutChangedOnSelectMultiTableSearch.RecordUrl's. A page that handles the picks is linked to no record pages.SearchChangedName | Type | Default | Description |
|---|---|---|---|
Collapsible | bool | False | Whether the box shows as a search button on a narrow screen (640 pixels or less) until the user opens it, as a header with little room has it. It folds away again once the user leaves it empty. |
KeyboardShortcut | bool | True | Whether Ctrl+K (⌘K on a Mac) anywhere on the page, and / where nothing is being typed, bring the box up and put the cursor in it. Default true; a page with two boxes leaves it on one. |
MaximumResults | int | 6 | How many rows are suggested: 1 to 25, 6 unless set. |
Placeholder | string? | The hint shown in the empty box, and its name to a screen reader. 'Search' unless set. | |
RecordUrl | Func<MultiTablePageItem, string>? | Where a row links to, in place of its record's page registered with null for no link. | |
SearchPageUrl* | string | The page that shows a Components.MultiTableSearch, which 'See all results' and a recent search go to with what to search for in MultiTableSearchBox.SearchParameterName: relative to the app's base, as a link is. | |
SearchParameterName | string | q | The search page's query parameter the search is given in: MultiTableSearch.SearchParameterName's. |
ShowRecent | bool | True | Whether the box offers the user's recent searches and the records they opened while nothing is typed, and keeps them as they search: in their browser, for each user apart. Default true. |
ShowRecordLinks | bool | True | Whether a row links to its record's page, registered with MultiTableSearchBox.RecordUrl nor MultiTableSearchBox.OnSelect is given. Default true. |
Tables | IEnumerable<string>? | The tables searched, by logical name. Unset, every table registered for search. | |
Width | string? | The box's width, as CSS: |
CollapsibleKeyboardShortcuttrue; a page with two boxes leaves it on one.MaximumResultsPlaceholderRecordUrlnull for no link.SearchPageUrl*Components.MultiTableSearch, which 'See all results' and a recent search go to with what to search for in MultiTableSearchBox.SearchParameterName: relative to the app's base, as a link is.SearchParameterNameMultiTableSearch.SearchParameterName's.ShowRecenttrue.ShowRecordLinksMultiTableSearchBox.RecordUrl nor MultiTableSearchBox.OnSelect is given. Default true.TablesWidthName | Type | Description |
|---|---|---|
OnSelect | EventCallback<MultiTablePageItem> | Raised when the user picks a row that has no link of MultiTableSearchBox.RecordUrl's. A page that handles the picks is linked to no record pages. |
OnSelectMultiTableSearchBox.RecordUrl's. A page that handles the picks is linked to no record pages.