Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/content/2.api/1.composables/2.helpers/use-sort-by.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ useSortBy(users, 'name')
// sort by the `name` attribute case insensitive
useSortBy(users, 'name', 'SORT_FLAG_CASE')

// sort by the `name` attribute locale aware
useSortBy(users, 'name', new Intl.Collator('lt'))

// sorts the collection by 'name' descending and then by 'lastname' ascending
useSortBy(users, [
['name', 'desc'],
Expand All @@ -36,6 +39,7 @@ useSortBy(users, (model) => model.age)
````ts
export type sorting<T> = ((record: T) => any) | string | [string, 'asc' | 'desc'][]
export type SortFlags = 'SORT_REGULAR' | 'SORT_FLAG_CASE'
export type SortComparator = SortFlags | Intl.Collator

export function useSortBy<T>(collection: T[], sort: sorting<T>, flags?: SortFlags): T[]
export function useSortBy<T>(collection: T[], sort: sorting<T>, flags?: SortComparator): T[]
````
13 changes: 12 additions & 1 deletion docs/content/2.api/3.query/order-by.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,21 @@ useRepo(User)

// Sort user name by its third character.
useRepo(User).orderBy(user => user.name[2]).get()

// Sort user names case insensitive.
useRepo(User).orderBy('name', 'asc', 'SORT_FLAG_CASE').get()

// Sort user names locale aware with an Intl.Collator,
// e.g. for Lithuanian: A, B, Š, T, U instead of A, B, T, U, Š.
useRepo(User).orderBy('name', 'asc', new Intl.Collator('lt')).get()
````

The optional third argument accepts either one of the sort flags (`'SORT_REGULAR'`, `'SORT_FLAG_CASE'`) or any [`Intl.Collator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator) instance. A collator is used to compare string values, which enables locale aware sorting as well as options like numeric ordering (`new Intl.Collator(undefined, { numeric: true })`).

## Typescript Declarations

````ts
function orderBy(field: OrderBy, direction: OrderDirection = 'asc'): Query
function orderBy(field: OrderBy, direction: OrderDirection = 'asc', flags: SortComparator = 'SORT_REGULAR'): Query

type SortComparator = 'SORT_REGULAR' | 'SORT_FLAG_CASE' | Intl.Collator
````
6 changes: 3 additions & 3 deletions packages/pinia-orm/src/composables/collection/useCollect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Collection, Model } from '../../../src'
import type { SortFlags } from '../../support/Utils'
import type { SortComparator } from '../../support/Utils'
import { useSum } from './useSum'
import { useMax } from './useMax'
import { useMin } from './useMin'
Expand All @@ -15,7 +15,7 @@ export interface UseCollect<M extends Model = Model> {
max: (field: string) => number
pluck: (field: string) => any[]
groupBy: (fields: string[] | string) => Record<string, Collection<M>>
sortBy: (sort: sorting<M>, flags?: SortFlags) => M[]
sortBy: (sort: sorting<M>, flags?: SortComparator) => M[]
keys: () => string[]
}

Expand All @@ -29,7 +29,7 @@ export function useCollect<M extends Model = Model> (models: Collection<M>): Use
max: field => useMax(models, field),
pluck: field => usePluck(models, field),
groupBy: fields => useGroupBy(models, fields),
sortBy: (sort, flags: SortFlags = 'SORT_REGULAR') => useSortBy(models, sort, flags),
sortBy: (sort, flags: SortComparator = 'SORT_REGULAR') => useSortBy(models, sort, flags),
keys: () => useKeys(models),
}
}
4 changes: 2 additions & 2 deletions packages/pinia-orm/src/composables/collection/useSortBy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SortFlags } from '../../support/Utils'
import type { SortComparator } from '../../support/Utils'
import { orderBy } from '../../support/Utils'

export type sorting<T> = ((record: T) => any) | string | [string, 'asc' | 'desc'][]
Expand All @@ -7,7 +7,7 @@ export type sorting<T> = ((record: T) => any) | string | [string, 'asc' | 'desc'
* Creates an array of elements, sorted in specified order by the results
* of running each element in a collection thru each iteratee.
*/
export function useSortBy<T extends Record<string, any>> (collection: T[], sort: sorting<T>, flags?: SortFlags): T[] {
export function useSortBy<T extends Record<string, any>> (collection: T[], sort: sorting<T>, flags?: SortComparator): T[] {
const directions = []
const iteratees = []

Expand Down
2 changes: 2 additions & 0 deletions packages/pinia-orm/src/query/Options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Model, WithKeys } from '../model/Model'
import type { SortComparator } from '../support/Utils'
import type { Query } from './Query'

export interface Where<T = Model> {
Expand All @@ -23,6 +24,7 @@ export interface WhereGroup {
export interface Order {
field: OrderBy
direction: OrderDirection
flags?: SortComparator
}

export interface Group {
Expand Down
8 changes: 5 additions & 3 deletions packages/pinia-orm/src/query/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isFunction,
orderBy,
} from '../support/Utils'
import type { SortComparator } from '../support/Utils'
import type { Collection, Element, Elements, GroupedCollection, Item, NormalizedData } from '../data/Data'
import type { Database } from '../database/Database'
import { Relation } from '../model/attributes/relations/Relation'
Expand Down Expand Up @@ -359,8 +360,8 @@ export class Query<M extends Model = Model> {
/**
* Add an "order by" clause to the query.
*/
orderBy (field: OrderBy, direction: OrderDirection = 'asc'): this {
this.orders.push({ field, direction })
orderBy (field: OrderBy, direction: OrderDirection = 'asc', flags: SortComparator = 'SORT_REGULAR'): this {
this.orders.push({ field, direction, flags })

return this
}
Expand Down Expand Up @@ -625,8 +626,9 @@ export class Query<M extends Model = Model> {
protected filterOrder (models: Collection<M>): Collection<M> {
const fields = this.orders.map(order => order.field)
const directions = this.orders.map(order => order.direction)
const flags = this.orders.map(order => order.flags ?? 'SORT_REGULAR')

return orderBy(models, fields, directions)
return orderBy(models, fields, directions, flags)
}

/**
Expand Down
5 changes: 3 additions & 2 deletions packages/pinia-orm/src/repository/Repository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Pinia } from 'pinia'
import type { Constructor } from '../types'
import { assert, isArray } from '../support/Utils'
import type { SortComparator } from '../support/Utils'
import type { Collection, Element, Item } from '../data/Data'
import type { Database } from '../database/Database'
import type { Model, WithKeys } from '../model/Model'
Expand Down Expand Up @@ -332,8 +333,8 @@ export class Repository<M extends Model = Model> {
/**
* Add an "order by" clause to the query.
*/
orderBy (field: OrderBy, direction?: OrderDirection): Query<M> {
return this.query().orderBy(field, direction)
orderBy (field: OrderBy, direction?: OrderDirection, flags?: SortComparator): Query<M> {
return this.query().orderBy(field, direction, flags)
}

/**
Expand Down
15 changes: 11 additions & 4 deletions packages/pinia-orm/src/support/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface SortableArray<T> {

export type SortFlags = 'SORT_REGULAR' | 'SORT_FLAG_CASE'

export type SortComparator = SortFlags | Intl.Collator

/**
* Compare two values with custom string operator
*/
Expand Down Expand Up @@ -77,7 +79,7 @@ export function orderBy<T extends Element> (
collection: T[],
iteratees: (((record: T) => any) | string)[],
directions: string[],
flags: SortFlags = 'SORT_REGULAR',
flags: SortComparator | SortComparator[] = 'SORT_REGULAR',
): T[] {
let index = -1

Expand Down Expand Up @@ -130,7 +132,7 @@ function compareMultiple<T> (
object: SortableArray<T>,
other: SortableArray<T>,
directions: string[],
flags: SortFlags,
flags: SortComparator | SortComparator[],
): number {
let index = -1

Expand All @@ -139,7 +141,7 @@ function compareMultiple<T> (
const length = objCriteria.length

while (++index < length) {
const result = compareAscending(objCriteria[index], othCriteria[index], flags)
const result = compareAscending(objCriteria[index], othCriteria[index], isArray(flags) ? flags[index] ?? 'SORT_REGULAR' : flags)

if (result) {
const direction = directions[index]
Expand All @@ -153,8 +155,13 @@ function compareMultiple<T> (
/**
* Compares values to sort them in ascending order.
*/
function compareAscending (value: any, other: any, flags: SortFlags): number {
function compareAscending (value: any, other: any, flags: SortComparator): number {
if (value !== other) {
if (typeof flags === 'object' && typeof value === 'string' && typeof other === 'string') {
const result = flags.compare(value, other)
return result > 0 ? 1 : result < 0 ? -1 : 0
}

const valIsDefined = value !== undefined
const valIsNull = value === null
const valIsReflexive = value === value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,34 @@ describe('feature/repository/retrieves_order_by', () => {
assertModels(users, expected)
})

it('can sort records locale aware with a collator', () => {
const userRepo = useRepo(User)

fillState({
users: {
1: { id: 1, name: 'T', age: 40 },
2: { id: 2, name: 'A', age: 30 },
3: { id: 3, name: 'Š', age: 20 },
4: { id: 4, name: 'U', age: 20 },
5: { id: 5, name: 'B', age: 50 },
},
})

const users = userRepo.orderBy('name', 'asc', new Intl.Collator('lt')).get()

const expected = [
{ id: 2, name: 'A', age: 30 },
{ id: 5, name: 'B', age: 50 },
{ id: 3, name: 'Š', age: 20 },
{ id: 1, name: 'T', age: 40 },
{ id: 4, name: 'U', age: 20 },
]

expect(users).toHaveLength(5)
assertInstanceOf(users, User)
assertModels(users, expected)
})

it('can sort nested records by pivot', () => {
Model.clearRegistries()
class User extends Model {
Expand Down
26 changes: 26 additions & 0 deletions packages/pinia-orm/tests/unit/support/Utils_Order_By.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,30 @@ describe('unit/support/Utils_Order_by', () => {

expect(orderBy(collection, [v => v.id], ['asc'])).toEqual(expected)
})

it('can order collection with a collator', () => {
const collection = [{ name: 'T' }, { name: 'Š' }, { name: 'A' }]

const expected = [{ name: 'A' }, { name: 'Š' }, { name: 'T' }]

expect(orderBy(collection, ['name'], ['asc'], new Intl.Collator('lt'))).toEqual(expected)
})

it('can order collection with different flags per field', () => {
const collection = [
{ name: 'T', group: 'b' },
{ name: 'Š', group: 'B' },
{ name: 'A', group: 'B' },
{ name: 'B', group: 'a' },
]

const expected = [
{ name: 'B', group: 'a' },
{ name: 'A', group: 'B' },
{ name: 'Š', group: 'B' },
{ name: 'T', group: 'b' },
]

expect(orderBy(collection, ['group', 'name'], ['asc', 'asc'], ['SORT_FLAG_CASE', new Intl.Collator('lt')])).toEqual(expected)
})
})
Loading