mirror of https://github.com/abpframework/abp.git
Browse Source
Added navigation property auto complete combobox for the extra propertiespull/6415/head
committed by
GitHub
13 changed files with 466 additions and 88 deletions
@ -0,0 +1,37 @@ |
|||||
|
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/StringUtils.cs#L155
|
||||
|
export function jsonNetCamelCase(str: string) { |
||||
|
if (!str || !isUpperCase(str[0])) return str; |
||||
|
|
||||
|
const chars = str.split(''); |
||||
|
const { length } = chars; |
||||
|
|
||||
|
for (let i = 0; i < length; i++) { |
||||
|
if (i === 1 && !isUpperCase(chars[i])) break; |
||||
|
|
||||
|
const hasNext = i + 1 < length; |
||||
|
|
||||
|
if (i > 0 && hasNext && !isUpperCase(chars[i + 1])) { |
||||
|
if (isSeparator(chars[i + 1])) { |
||||
|
chars[i] = toLowerCase(chars[i]); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
chars[i] = toLowerCase(chars[i]); |
||||
|
} |
||||
|
|
||||
|
return chars.join(''); |
||||
|
} |
||||
|
|
||||
|
function isSeparator(str = '') { |
||||
|
return /[\s\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]+/.test(str); |
||||
|
} |
||||
|
|
||||
|
function isUpperCase(str = '') { |
||||
|
return /[A-Z]+/.test(str); |
||||
|
} |
||||
|
|
||||
|
function toLowerCase(str = '') { |
||||
|
return str.toLowerCase(); |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
import { ABP, ExtensionPropertyUiLookupDto, RestService } from '@abp/ng.core'; |
||||
|
import { Observable, of } from 'rxjs'; |
||||
|
import { map } from 'rxjs/operators'; |
||||
|
import { ePropType } from '../enums/props.enum'; |
||||
|
import { PropCallback } from '../models/props'; |
||||
|
|
||||
|
export function createTypeaheadOptions( |
||||
|
lookup: ExtensionPropertyUiLookupDto, |
||||
|
): PropCallback<any, Observable<ABP.Option<any>[]>> { |
||||
|
return (data, searchText) => |
||||
|
searchText |
||||
|
? data |
||||
|
.getInjected(RestService) |
||||
|
.request( |
||||
|
{ |
||||
|
method: 'GET', |
||||
|
url: lookup.url, |
||||
|
params: { |
||||
|
[lookup.filterParamName]: searchText, |
||||
|
}, |
||||
|
}, |
||||
|
{ apiName: 'Default' }, |
||||
|
) |
||||
|
.pipe( |
||||
|
map(response => { |
||||
|
const list = response[lookup.resultListPropertyName]; |
||||
|
const mapToOption = (item: any) => ({ |
||||
|
key: item[lookup.displayPropertyName], |
||||
|
value: item[lookup.valuePropertyName], |
||||
|
}); |
||||
|
return list.map(mapToOption); |
||||
|
}), |
||||
|
) |
||||
|
: of([]); |
||||
|
} |
||||
|
|
||||
|
export function getTypeaheadType(lookup: ExtensionPropertyUiLookupDto, name: string) { |
||||
|
return Boolean(lookup.url) |
||||
|
? ePropType.Typeahead |
||||
|
: name.endsWith('_Text') |
||||
|
? ePropType.Hidden |
||||
|
: undefined; |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
import { jsonNetCamelCase } from '../lib/utils/case.util'; |
||||
|
|
||||
|
describe('Case Utils', () => { |
||||
|
describe('#jsonNetCamelCase', () => { |
||||
|
test.each` |
||||
|
input | output |
||||
|
${'Primary'} | ${'primary'} |
||||
|
${'PrimaryRole'} | ${'primaryRole'} |
||||
|
${'Primary Role'} | ${'primary Role'} |
||||
|
${'PrimaryRole_Text'} | ${'primaryRole_Text'} |
||||
|
${'ISBN'} | ${'isbn'} |
||||
|
${''} | ${''} |
||||
|
${'iMDB'} | ${'iMDB'} |
||||
|
`('should return $output when input is $input', ({ input, output }) => {
|
||||
|
expect(jsonNetCamelCase(input)).toBe(output); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,72 @@ |
|||||
|
import { ExtensionPropertyUiLookupDto } from '@abp/ng.core'; |
||||
|
import { of } from 'rxjs'; |
||||
|
import { createTypeaheadOptions } from '../lib/utils/typeahead.util'; |
||||
|
|
||||
|
const lookup: ExtensionPropertyUiLookupDto = { |
||||
|
url: 'url', |
||||
|
resultListPropertyName: 'list', |
||||
|
displayPropertyName: 'text', |
||||
|
valuePropertyName: 'id', |
||||
|
filterParamName: 'filter', |
||||
|
}; |
||||
|
|
||||
|
describe('Typeahead Utils', () => { |
||||
|
describe('#createTypeaheadOptions', () => { |
||||
|
it('should return observable empty array when search text does not exist', async () => { |
||||
|
const list = await createTypeaheadOptions(null)(null, null).toPromise(); |
||||
|
expect(list).toEqual([]); |
||||
|
}); |
||||
|
|
||||
|
it('should call request method of RestService with lookup url, filter param and search text', async () => { |
||||
|
const data = createData([]); |
||||
|
const service = data.getInjected(); |
||||
|
await createTypeaheadOptions(lookup)(data, 'x').toPromise(); |
||||
|
expect(service.request).toHaveBeenCalledTimes(1); |
||||
|
expect(service.request).toHaveBeenCalledWith( |
||||
|
{ |
||||
|
method: 'GET', |
||||
|
url: 'url', |
||||
|
params: { |
||||
|
filter: 'x', |
||||
|
}, |
||||
|
}, |
||||
|
{ apiName: 'Default' }, |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
it('should return options based on given lookup data', async () => { |
||||
|
const data = createData([ |
||||
|
{ |
||||
|
text: 'foo', |
||||
|
id: 'bar', |
||||
|
}, |
||||
|
{ |
||||
|
text: 'baz', |
||||
|
id: 'qux', |
||||
|
}, |
||||
|
]); |
||||
|
|
||||
|
const options = await createTypeaheadOptions(lookup)(data, 'x').toPromise(); |
||||
|
expect(options).toEqual([ |
||||
|
{ |
||||
|
key: 'foo', |
||||
|
value: 'bar', |
||||
|
}, |
||||
|
{ |
||||
|
key: 'baz', |
||||
|
value: 'qux', |
||||
|
}, |
||||
|
]); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
function createData(list: { text: string; id: string }[]): any { |
||||
|
const service = { request: jest.fn(() => of({ list })) }; |
||||
|
|
||||
|
return { |
||||
|
getInjected: () => service, |
||||
|
index: 0, |
||||
|
record: null, |
||||
|
}; |
||||
|
} |
||||
Loading…
Reference in new issue