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
4 changes: 2 additions & 2 deletions client-app/src/admin/tests/viewmanager/ViewManagerTest.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
}
}

.xh-form-field-switch-input {
.xh-form-field--switch-input {
margin-left: 4px;
.xh-form-field-label {
.xh-form-field__label {
font-size: 0.8em;
}
}
Expand Down
2 changes: 1 addition & 1 deletion client-app/src/apps/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {AuthModel} from '../core/AuthModel';

XH.renderApp({
clientAppCode: 'contact',
clientAppName: 'XH Contact',
clientAppName: 'XH Contacts',
componentClass: AppComponent,
modelClass: AppModel,
containerClass: AppContainer,
Expand Down
88 changes: 45 additions & 43 deletions client-app/src/desktop/tabs/forms/FormPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {form} from '@xh/hoist/cmp/form';
import {div, filler, hbox, hframe, span, vbox} from '@xh/hoist/cmp/layout';
import {box, div, filler, hbox, hframe, span, vbox} from '@xh/hoist/cmp/layout';
import {creates, hoistCmp} from '@xh/hoist/core';
import {button} from '@xh/hoist/desktop/cmp/button';
import {formField} from '@xh/hoist/desktop/cmp/form';
Expand Down Expand Up @@ -59,7 +59,6 @@ export const formPanel = hoistCmp.factory({

const formContent = hoistCmp.factory<FormPanelModel>(({model}) =>
panel({
flex: 1,
item: form({
fieldDefaults: {
inline: model.inline,
Expand All @@ -72,23 +71,14 @@ const formContent = hoistCmp.factory<FormPanelModel>(({model}) =>
className: 'tb-form-panel__inner-scroll',
items: [
hbox({
flex: 'none',
gap: 10,
items: [
vbox({
flex: 1,
marginRight: 30,
items: [
hbox(
formField({field: 'firstName', item: textInput()}),
formField({field: 'lastName', item: textInput()})
),
region(),
email(),
tags()
]
div({
style: {width: '50%'},
items: [firstAndLastNames(), email(), region(), tags()]
}),
vbox({
flex: 1,
div({
style: {width: '50%'},
items: [
startAndEndDate(),
reasonForLeaving(),
Expand All @@ -107,6 +97,22 @@ const formContent = hoistCmp.factory<FormPanelModel>(({model}) =>
})
);

const firstAndLastNames = hoistCmp.factory<FormPanelModel>(({model}) => {
return box({
flexDirection: model.inline ? 'column' : 'row',
items: [
formField({
field: 'firstName',
item: textInput()
}),
formField({
field: 'lastName',
item: textInput()
})
]
});
});

const email = hoistCmp.factory(() =>
formField({
field: 'email',
Expand Down Expand Up @@ -137,27 +143,21 @@ const tags = hoistCmp.factory(() =>
})
);

const startAndEndDate = hoistCmp.factory(() =>
hbox(
formField({
field: 'startDate',
flex: 1,
inline: false, // always print labels on top (override form-level inline)
item: dateInput({
valueType: 'localDate'
})
}),
formField({
field: 'endDate',
flex: 1,
inline: false,
item: dateInput({
valueType: 'localDate',
enableClear: true
const startAndEndDate = hoistCmp.factory<FormPanelModel>(({model}) => {
return box({
flexDirection: model.inline ? 'column' : 'row',
items: [
formField({
field: 'startDate',
item: dateInput({valueType: 'localDate', width: 150})
}),
formField({
field: 'endDate',
item: dateInput({valueType: 'localDate', width: 150, enableClear: true})
})
})
)
);
]
});
});

const reasonForLeaving = hoistCmp.factory(() =>
formField({
Expand All @@ -168,22 +168,24 @@ const reasonForLeaving = hoistCmp.factory(() =>
})
);

const managerAndYearsExperience = hoistCmp.factory<FormPanelModel>(({model}) =>
hbox({
const managerAndYearsExperience = hoistCmp.factory<FormPanelModel>(({model}) => {
return box({
flexDirection: model.inline ? 'column' : 'row',
items: [
formField({
field: 'isManager',
label: 'Manager?',
width: 100,
flex: 'none',
item: checkbox()
}),
formField({
field: 'yearsExperience',
item: numberInput({width: 50})
})
],
alignItems: model.inline ? 'center' : 'top'
})
);
]
});
});

const notes = hoistCmp.factory(() =>
formField({
Expand Down
44 changes: 35 additions & 9 deletions client-app/src/desktop/tabs/forms/FormPanelModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {HoistModel, managed, TaskObserver, XH} from '@xh/hoist/core';
import {FormModel} from '@xh/hoist/cmp/form';
import {pre, vbox} from '@xh/hoist/cmp/layout';
import {HoistModel, managed, TaskObserver, XH} from '@xh/hoist/core';
import {
constrainAll,
dateIs,
Expand All @@ -9,11 +10,10 @@ import {
stringExcludes,
validEmail
} from '@xh/hoist/data';
import {pre, vbox} from '@xh/hoist/cmp/layout';
import {Icon} from '@xh/hoist/icon';
import {bindable, makeObservable} from '@xh/hoist/mobx';
import {LocalDate} from '@xh/hoist/utils/datetime';
import {Icon} from '@xh/hoist/icon';
import {filter, isEmpty, isNil} from 'lodash';
import {filter, isEmpty} from 'lodash';

export class FormPanelModel extends HoistModel {
@managed
Expand Down Expand Up @@ -55,14 +55,31 @@ export class FormPanelModel extends HoistModel {
name: 'yearsExperience',
rules: [
numberIs({min: 0, max: 100}),
({value}) =>
value > 50
? {
severity: 'info',
message: 'You have extensive experience!'
}
: null,
{
when: (f, {isManager}) => isManager,
check: [
required,
({value}) =>
isNil(value) || value < 10
? 'Managerial positions require at least 10 years of experience.'
: null
({value}) => {
if (value < 10) {
return {
severity: 'error',
message: '10+ years required for managers.'
};
}
if (value < 15) {
return {
severity: 'warning',
message: '15+ years recommended for managers.'
};
}
}
]
}
]
Expand Down Expand Up @@ -91,7 +108,16 @@ export class FormPanelModel extends HoistModel {
},
{
name: 'region',
rules: [required]
rules: [
required,
({value}) =>
['London', 'Montreal'].includes(value)
? {
severity: 'warning',
message: 'Region is outside primary operating areas.'
}
: null
]
},
{
name: 'tags',
Expand Down
2 changes: 1 addition & 1 deletion client-app/src/desktop/tabs/forms/InputsPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
text-overflow: ellipsis;
}

.xh-form-field .xh-form-field-label {
.xh-form-field .xh-form-field__label {
font-size: var(--xh-font-size-large-px);
margin-bottom: var(--xh-pad-half-px);
}
Expand Down
19 changes: 15 additions & 4 deletions client-app/src/desktop/tabs/grids/InlineEditingPanelModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,28 @@ export class InlineEditingPanelModel extends HoistModel {
when: (f, {category}) => category === 'US',
check: async ({value}) => {
if (this.asyncValidation) await wait(1000);
return isNil(value) || value < 10
? 'Records where `category` is "US" require `amount` of 10 or greater.'
: null;
if (isNil(value) || value < 10) {
return 'Records where `category` is "US" require `amount` of 10 or greater.';
} else if (value > 50) {
return {
severity: 'warning',
message: 'Amounts over 50 may require additional approval.'
};
}
}
}
]
},
{
name: 'date',
type: 'localDate',
rules: [dateIs({min: LocalDate.today().startOfYear(), max: 'today'})]
rules: [
dateIs({min: LocalDate.today().startOfYear(), max: 'today'}),
({value}) =>
value && !value.isWeekday
? {severity: 'info', message: 'Date falls on a weekend.'}
: null
]
},
{
name: 'restricted',
Expand Down
2 changes: 1 addition & 1 deletion client-app/src/desktop/tabs/other/formats/Formats.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
}
}

.xh-form-field-label {
.xh-form-field__label {
width: 200px;
font-family: var(--xh-font-family-mono);
}
Expand Down
2 changes: 2 additions & 0 deletions client-app/src/examples/contact/DirectoryPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ const tbar = hoistCmp.factory<DirectoryPanelModel>(({model}) => {
items: [
button({
text: 'Details',
icon: Icon.list(),
value: 'grid',
width: 80
}),
button({
text: 'Faces',
value: 'tiles',
icon: Icon.userCircle(),
width: 80
})
]
Expand Down
41 changes: 22 additions & 19 deletions client-app/src/examples/contact/DirectoryPanelModel.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {HoistModel, managed, persist, XH} from '@xh/hoist/core';
import {HoistModel, LoadSpec, managed, persist, XH} from '@xh/hoist/core';
import {action, bindable, makeObservable, observable, runInAction} from '@xh/hoist/mobx';
import {div, hbox} from '@xh/hoist/cmp/layout';
import {GridModel} from '@xh/hoist/cmp/grid';
import {withFilterByField, withFilterByKey} from '@xh/hoist/data';
import {StoreRecord, withFilterByField, withFilterByKey} from '@xh/hoist/data';
import {isEmpty, uniq, without} from 'lodash';

import {PERSIST_APP} from './AppModel';
Expand Down Expand Up @@ -50,28 +50,28 @@ export class DirectoryPanelModel extends HoistModel {
const gridModel = (this.gridModel = this.createGridModel());
this.detailsPanelModel = new DetailsPanelModel(this);

this.addReaction({
track: () => gridModel.selectedRecord,
run: rec => this.detailsPanelModel.setCurrentRecord(rec)
});

this.addReaction({
track: () => this.locationFilter,
run: () => this.updateLocationFilter()
});

this.addReaction({
track: () => this.tagFilters,
run: () => this.updateTagFilter()
});
this.addReaction(
{
track: () => gridModel.selectedRecord,
run: rec => this.detailsPanelModel.setCurrentRecord(rec)
},
{
track: () => this.locationFilter,
run: () => this.updateLocationFilter()
},
{
track: () => this.tagFilters,
run: () => this.updateTagFilter()
}
);
}

async updateContactAsync(id, data) {
await XH.contactService.updateContactAsync(id, data);
await this.loadAsync();
}

toggleFavorite(record) {
toggleFavorite(record: StoreRecord) {
XH.contactService.toggleFavorite(record.id);
// Update store directly, to avoid more heavyweight full reload.
this.gridModel.store.modifyRecords({id: record.id, isFavorite: !record.data.isFavorite});
Expand All @@ -80,19 +80,22 @@ export class DirectoryPanelModel extends HoistModel {
//------------------------
// Implementation
//------------------------
override async doLoadAsync(loadSpec) {
override async doLoadAsync(loadSpec: LoadSpec) {
const {gridModel} = this;

try {
const contacts = await XH.contactService.getContactsAsync();
if (loadSpec.isStale) return;

runInAction(() => {
this.tagList = uniq(contacts.flatMap(it => it.tags ?? [])).sort() as string[];
this.locationList = uniq(contacts.map(it => it.location)).sort() as string[];
});

gridModel.loadData(contacts);
gridModel.preSelectFirstAsync();
await gridModel.preSelectFirstAsync();
} catch (e) {
if (loadSpec.isStale) return;
XH.handleException(e);
}
}
Expand Down
Loading
Loading