Skip to content
Draft
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
30 changes: 30 additions & 0 deletions client-app/src/admin/tests/select/SelectTestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ export class SelectTestModel extends HoistModel {
@bindable.ref
enableMultiMenuOpen: string[];

// ID value + lookupFn examples, pre-populated to verify label (not raw id) on mount
@bindable
idNotInOpts: number = 99;

@bindable
idQueryLookup: number = 3;

constructor() {
super();
makeObservable(this);
Expand All @@ -51,4 +58,27 @@ export class SelectTestModel extends HoistModel {
fireImmediately: true
});
}

// All of the records to power the select option and lookupFn.
get employees(): any[] {
return [
{id: 1, name: 'Alice Chen', isActive: true},
{id: 2, name: 'Bob Park', isActive: true},
{id: 3, name: 'Carol Diaz', isActive: true},
{id: 4, name: 'Dave Kim', isActive: false},
{id: 5, name: 'Eve Singh', isActive: true},
{id: 6, name: 'Fred Rogers', isActive: true},
{id: 99, name: 'Zara Quinn', isActive: false}
];
}

// Only active employee records are selectable.
get selectableEmployees() {
return this.employees.filter(it => it.isActive);
}

// Lookup returns both selectable and not-selectable records.
lookupEmployeeById(id: number) {
return this.employees.find(it => it.id === id);
}
}
34 changes: 33 additions & 1 deletion client-app/src/admin/tests/select/SelectTestPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,33 @@ export const SelectTestPanel = hoistCmp({
hideSelectedOptions: false,
closeMenuOnSelect: false
}
}),
// ID value + lookupFn examples, all pre-populated with a numeric id.
// Should display the label on mount, not the raw id number.
example({
name: 'starting ID value, options (value not in list), lookupFn',
bind: 'idNotInOpts',
selectProps: {
options: model.selectableEmployees,
lookupFn: id => model.lookupEmployeeById(id),
labelField: 'name',
valueField: 'id',
enableClear: true,
placeholder: 'Select an employee...'
}
}),
example({
name: 'starting ID value, queryFn, lookupFn',
bind: 'idQueryLookup',
selectProps: {
width: 200,
valueField: 'id',
labelField: 'company',
enableClear: true,
queryFn: queryCustomersAsync,
lookupFn: lookupCustomerByIdAsync,
placeholder: 'Search customers...'
}
})
]
})
Expand Down Expand Up @@ -179,10 +206,15 @@ const customerProps = {
async function queryCustomersAsync(query) {
return XH.fetchJson({
url: 'customer',
params: {query}
params: {query, activeOnly: true}
});
}

// Resolves a single customer id to its full option, for display before any query runs.
async function lookupCustomerByIdAsync(id: number) {
return XH.fetchJson({url: 'customer', params: {id}});
}

const recipes = [
{
label: 'Hot Tea',
Expand Down
32 changes: 31 additions & 1 deletion client-app/src/mobile/form/FormPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,34 @@ const formCmp = hoistCmp.factory<FormPageModel>(({model}) => {
item: searchInput()
})
]
}),
formFieldSet({
className: 'xh-margin-top',
title: 'Field Set 3',
modelConfig: {collapsible: true},
items: [
formField({
field: 'employeeId',
item: select({
placeholder: 'Select an employee...',
options: model.selectableEmployees,
lookupFn: id => model.lookupEmployeeById(id),
valueField: 'id',
labelField: 'name'
})
}),
formField({
field: 'customerId',
item: select({
placeholder: 'Search customers...',
title: 'Search customers...',
enableFilter: true,
enableFullscreen: true,
queryFn: q => model.queryCustomersAsync(q),
lookupFn: v => model.lookupCustomerByIdAsync(v)
})
})
]
})
)
})
Expand All @@ -173,7 +201,9 @@ const results = hoistCmp.factory(() => {
fieldResult({field: 'enabled', renderer: v => (v ? 'Yes' : 'No')}),
fieldResult({field: 'buttonGroup'}),
fieldResult({field: 'notes'}),
fieldResult({field: 'searchQuery'})
fieldResult({field: 'searchQuery'}),
fieldResult({field: 'employeeId'}),
fieldResult({field: 'customerId'})
]
});
});
Expand Down
37 changes: 35 additions & 2 deletions client-app/src/mobile/form/FormPageModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,49 @@ export class FormPageModel extends HoistModel {
{name: 'enabled'},
{name: 'buttonGroup', initialValue: 'button2'},
{name: 'notes'},
{name: 'searchQuery', displayName: 'Search'}
{name: 'searchQuery', displayName: 'Search'},
{name: 'employeeId', displayName: 'Employee (using ID)', initialValue: 99},
{name: 'customerId', displayName: 'Customer (using ID)', initialValue: 6} // Multiples of 3 are inactive.
]
});

// All of the records to power the select option and lookupFn.
get employees(): any[] {
return [
{id: 1, name: 'Alice Chen', isActive: true},
{id: 2, name: 'Bob Park', isActive: true},
{id: 3, name: 'Carol Diaz', isActive: true},
{id: 4, name: 'Dave Kim', isActive: false},
{id: 5, name: 'Eve Singh', isActive: true},
{id: 6, name: 'Fred Rogers', isActive: true},
{id: 99, name: 'Zara Quinn', isActive: false}
];
}

// Only some of the employee records are selectable.
get selectableEmployees() {
return this.employees.filter(it => it.isActive);
}

// Lookup returns both selectable and not-selectable records.
lookupEmployeeById(id: number) {
return this.employees.find(it => it.id === id);
}

async queryCustomersAsync(query) {
const results = await XH.fetchJson({url: 'customer', params: {query}});
const results = await XH.fetchJson({
url: 'customer',
params: {query, activeOnly: true}
});
return results.map(it => {
const value = it.id,
label = it.company;
return {value, label};
});
}

async lookupCustomerByIdAsync(id) {
const result = await XH.fetchJson({url: 'customer', params: {id}});
return {value: result.id, label: result.company};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ class CustomerController extends BaseController {
def customerService

def index() {
renderJSON(customerService.queryCustomers(params.query))
def id = params.id as Long,
query = params.query as String,
activeOnly = params.activeOnly as Boolean

if (id && query) throw new RuntimeException('Cannot specify both query and id')
if (id) {
renderJSON(customerService.getCustomer(id))
return
}
renderJSON(customerService.queryCustomers(query, activeOnly))
}
}
15 changes: 12 additions & 3 deletions grails-app/services/io/xh/toolbox/data/CustomerService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,21 @@ class CustomerService extends BaseService {
allCustomers = loadCustomersFromFile()
}

List<Map> queryCustomers(String query) {
if (!query) return allCustomers
Map getCustomer(Long id) {
if (!id) return null

return allCustomers.find { it.id == id }
}

List<Map> queryCustomers(String query, Boolean activeOnly = false) {
if (!query) {
return allCustomers.findAll { !activeOnly || it.isActive }
}

def q = query.toUpperCase()
return allCustomers.findAll {
it.company.toUpperCase().startsWith(q) || it.city.toUpperCase().startsWith(q)
(it.company.toUpperCase().startsWith(q) || it.city.toUpperCase().startsWith(q)) &&
(!activeOnly || it.isActive)
}
}

Expand Down
Loading