Skip to content

Commit

Permalink
Merge pull request #3155 from Tanddant/v4-ODataFilterQuery
Browse files Browse the repository at this point in the history
OData lambda style filter query
  • Loading branch information
bcameron1231 authored Nov 4, 2024
2 parents ac62455 + 759bbcf commit a6611e0
Show file tree
Hide file tree
Showing 3 changed files with 412 additions and 5 deletions.
97 changes: 97 additions & 0 deletions docs/sp/items.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,89 @@ const r = await sp.web.lists.getByTitle("TaxonomyList").getItemsByCAMLQuery({
});
```

### Filter using fluent filter

>Note: This feature is currently in preview and may not work as expected.
PnPjs supports a fluent filter for all OData endpoints, including the items endpoint. this allows you to write a strongly fluent filter that will be parsed into an OData filter.

```TypeScript
import { spfi } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";

const sp = spfi(...);

const r = await sp.web.lists.filter(l => l.number("ItemCount").greaterThan(5000))();
```

The following field types are supported in the fluent filter:

- Text
- Choice
- MultiChoice
- Number
- Date
- Boolean
- Lookup
- LookupId

The following operations are supported in the fluent filter:

| Field Type | Operators/Values |
| -------------------- | -------------------------------------------------------------------------------------------- |
| All field types | `equals`, `notEquals`, `in`, `notIn` |
| Text & choice fields | `startsWith`, `contains` |
| Numeric fields | `greaterThan`, `greaterThanOrEquals`, `lessThan`, `lessThanOrEquals` |
| Date fields | `greaterThan`, `greaterThanOrEquals`, `lessThan`, `lessThanOrEquals`, `isBetween`, `isToday` |
| Boolean fields | `isTrue`, `isFalse`, `isFalseOrNull` |
| Lookup | `id`, Text and Number field types |

#### Complex Filter

For all the regular endpoints, the fluent filter will infer the type automatically, but for the list items filter, you'll need to provide your own types to make the parser work.

You can use the `and` and `or` operators to create complex filters that nest different grouping.

```TypeScript
import { spfi } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";

const sp = spfi(...);

interface ListItem extends IListItem {
FirstName: string;
LastName: string;
Age: number;
Manager: IListItem;
StartDate: Date;
}


// Get all employees named John
const r = await sp.web.lists.getByTitle("ListName").items.filter<ListItem>(f => f.text("FirstName").equal("John"))();

// Get all employees not named John who are over 30
const r1 = await sp.web.lists.getByTitle("ListName").items.filter<ListItem>(f => f.text("FirstName").notEquals("John").and().number("Age").greaterThan(30))();

// Get all employees that are named John Doe or Jane Doe
const r2 = await sp.web.lists.getByTitle("ListName").items.filter<ListItem>(f => f.or(
f.and(
f.text("FirstName").equals("John"),
f.text("LastName").equals("Doe")
),
f.and(
f.text("FirstName").equals("Jane"),
f.text("LastName").equals("Doe")
)
))();

// Get all employees who are managed by John and start today
const r3 = await sp.web.lists.getByTitle("ListName").items.filter<ListItem>(f => f.lookup("Manager").text("FirstName").equals("John").and().date("StartDate").isToday())();
```

### Retrieving PublishingPageImage

The PublishingPageImage and some other publishing-related fields aren't stored in normal fields, rather in the MetaInfo field. To get these values you need to use the technique shown below, and originally outlined in [this thread](https://github.com/SharePoint/PnP-JS-Core/issues/178). Note that a lot of information can be stored in this field so will pull back potentially a significant amount of data, so limit the rows as possible to aid performance.
Expand Down Expand Up @@ -326,6 +409,8 @@ const sp = spfi(...);

// you are getting back a collection here
const items: any[] = await sp.web.lists.getByTitle("MyList").items.top(1).filter("Title eq 'A Title'")();
// Using fluent filter
const items1: any[] = await sp.web.lists.getByTitle("MyList").items.top(1).filter(f => f.text("Title").equals("A Title"))();

// see if we got something
if (items.length > 0) {
Expand Down Expand Up @@ -425,6 +510,9 @@ const sp = spfi(...);
// first we need to get the hidden field's internal name.
// The Title of that hidden field is, in my case and in the linked article just the visible field name with "_0" appended.
const fields = await sp.web.lists.getByTitle("TestList").fields.filter("Title eq 'MultiMetaData_0'").select("Title", "InternalName")();
// Using fluent filter
const fields1 = await sp.web.lists.getByTitle("TestList").fields.filter(f => f.text("Title").equals("MultiMetaData_0")).select("Title", "InternalName")();

// get an item to update, here we just create one for testing
const newItem = await sp.web.lists.getByTitle("TestList").items.add({
Title: "Testing",
Expand Down Expand Up @@ -593,6 +681,15 @@ const response =
.filter(`Hidden eq false and Title eq '[Field's_Display_Name]'`)
();

// Using fluent filter
const response1 =
await sp.web.lists
.getByTitle('[Lists_Title]')
.fields
.select('Title, EntityPropertyName')
.filter(l => l.boolean("Hidden").isFalse().and().text("Title").equals("[Field's_Display_Name]"))
();

console.log(response.map(field => {
return {
Title: field.Title,
Expand Down
15 changes: 12 additions & 3 deletions docs/sp/webs.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,15 @@ const infos2 = await web.webinfos.select("Title", "Description")();

// or filter
const infos3 = await web.webinfos.filter("Title eq 'MyWebTitle'")();
// Using fluent filter
const infos4 = await web.webinfos.filter(w => w.text("Title").equals('MyWebTitle'))();


// or both
const infos4 = await web.webinfos.select("Title", "Description").filter("Title eq 'MyWebTitle'")();
const infos5 = await web.webinfos.select("Title", "Description").filter(w => w.text("Title").equals('MyWebTitle'))();

// get the top 4 ordered by Title
const infos5 = await web.webinfos.top(4).orderBy("Title")();
const infos6 = await web.webinfos.top(4).orderBy("Title")();
```

> Note: webinfos returns [IWebInfosData](#IWebInfosData) which is a subset of all the available fields on IWebInfo.
Expand Down Expand Up @@ -537,9 +540,12 @@ const folders = await sp.web.folders();

// you can also filter and select as with any collection
const folders2 = await sp.web.folders.select("ServerRelativeUrl", "TimeLastModified").filter("ItemCount gt 0")();
// Using fluent filter
const folders3 = await sp.web.folders.select("ServerRelativeUrl", "TimeLastModified").filter(f => f.number("ItemCount").greaterThan(0))();


// or get the most recently modified folder
const folders2 = await sp.web.folders.orderBy("TimeLastModified").top(1)();
const folders4 = await sp.web.folders.orderBy("TimeLastModified").top(1)();
```

### rootFolder
Expand Down Expand Up @@ -856,6 +862,9 @@ const users = await sp.web.siteUsers();
const users2 = await sp.web.siteUsers.top(5)();

const users3 = await sp.web.siteUsers.filter(`startswith(LoginName, '${encodeURIComponent("i:0#.f|m")}')`)();
// Using fluent filter
const user4 = await sp.web.siteUsers.filter(u => u.text("LoginName").startsWith(encodeURIComponent("i:0#.f|m")))();

```

### currentUser
Expand Down
Loading

0 comments on commit a6611e0

Please sign in to comment.