Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 packages/components/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@labkey/components",
"version": "7.8.1",
"version": "7.9.0",
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
"sideEffects": false,
"files": [
Expand Down
6 changes: 5 additions & 1 deletion packages/components/releaseNotes/components.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# @labkey/components
Components, models, actions, and utility functions for LabKey applications and pages

### version 7.8.1
### version 7.9.0
*Released*: 6 January 2026
- GitHub Issue 503: Field editor URL option to set target window (i.e. _blank)

### version 7.8.1
*Released*: 5 January 2026
- GitHub 562: Combine warning messages for file preview unknown/system fields with the warning for duplicate columns
- FilePreviewGrid to combine warningMsg with previewData.warningMsg in a single Alert

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ export class DomainRowExpandedOptions extends React.Component<Props> {
field,
index,
onChange,
onMultiChange,
showingModal,
appPropertiesOnly,
domainIndex,
Expand Down Expand Up @@ -311,6 +312,7 @@ export class DomainRowExpandedOptions extends React.Component<Props> {
domainIndex={domainIndex}
field={field}
onChange={onChange}
onMultiChange={onMultiChange}
appPropertiesOnly={appPropertiesOnly}
domainFormDisplayOptions={domainFormDisplayOptions}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { createFormInputId } from './utils';
import {
CALCULATED_CONCEPT_URI,
DOMAIN_FIELD_DESCRIPTION,
DOMAIN_FIELD_FULLY_LOCKED,
DOMAIN_FIELD_IMPORTALIASES,
DOMAIN_FIELD_LABEL,
DOMAIN_FIELD_ONTOLOGY_PRINCIPAL_CONCEPT,
DOMAIN_FIELD_URL,
DOMAIN_FIELD_URL_TARGET,
STORAGE_UNIQUE_ID_CONCEPT_URI,
STRING_RANGE_URI,
} from './constants';
Expand All @@ -29,6 +31,7 @@ const field = DomainField.create({
label: _label,
importAliases: _importAliases,
URL: _URL,
isTargetBlank: true,
propertyURI: 'test',
});

Expand All @@ -50,6 +53,15 @@ const calculatedField = DomainField.create({
conceptURI: CALCULATED_CONCEPT_URI,
});

const lockedField = DomainField.create({
name: 'lockedField',
rangeURI: STRING_RANGE_URI,
propertyId: 3,
description: 'locked field desc',
label: 'Locked Field',
lockType: DOMAIN_FIELD_FULLY_LOCKED,
});

const DEFAULT_PROPS = {
index: 1,
domainIndex: 1,
Expand All @@ -73,21 +85,31 @@ describe('NameAndLinkingOptions', () => {
let formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_DESCRIPTION, 1, 1));
expect(formField.length).toEqual(1);
expect(formField[0].textContent).toEqual(_description);
expect(formField[0].hasAttribute('disabled')).toEqual(false);

// Label
formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_LABEL, 1, 1));
expect(formField.length).toEqual(1);
expect(formField[0].getAttribute('value')).toEqual(_label);
expect(formField[0].hasAttribute('disabled')).toEqual(false);

// Aliases
formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_IMPORTALIASES, 1, 1));
expect(formField.length).toEqual(1);
expect(formField[0].getAttribute('value')).toEqual(_importAliases);
expect(formField[0].hasAttribute('disabled')).toEqual(false);

// URL
formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_URL, 1, 1));
expect(formField.length).toEqual(1);
expect(formField[0].getAttribute('value')).toEqual(_URL);
expect(formField[0].hasAttribute('disabled')).toEqual(false);

// URL Target
formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_URL_TARGET, 1, 1));
expect(formField.length).toEqual(1);
expect(formField[0].hasAttribute('checked')).toEqual(true);
expect(formField[0].hasAttribute('disabled')).toEqual(false);

expect(container).toMatchSnapshot();
});
Expand Down Expand Up @@ -119,6 +141,13 @@ describe('NameAndLinkingOptions', () => {
test('calculated field', () => {
render(<NameAndLinkingOptions {...DEFAULT_PROPS} field={calculatedField} />);
expect(document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_IMPORTALIASES, 1, 1))).toHaveLength(0);

expect(document.querySelector('#' + createFormInputId(DOMAIN_FIELD_URL, 1, 1)).getAttribute('value')).toEqual(
''
);
expect(
document.querySelector('#' + createFormInputId(DOMAIN_FIELD_URL_TARGET, 1, 1)).hasAttribute('checked')
).toEqual(false);
});

test('hideImportAliases', () => {
Expand All @@ -132,4 +161,23 @@ describe('NameAndLinkingOptions', () => {
);
expect(document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_IMPORTALIASES, 1, 1))).toHaveLength(0);
});

test('locked field', () => {
render(<NameAndLinkingOptions {...DEFAULT_PROPS} field={lockedField} />);
expect(
document.querySelector('#' + createFormInputId(DOMAIN_FIELD_LABEL, 1, 1)).hasAttribute('disabled')
).toEqual(true);
expect(
document.querySelector('#' + createFormInputId(DOMAIN_FIELD_DESCRIPTION, 1, 1)).hasAttribute('disabled')
).toEqual(true);
expect(
document.querySelector('#' + createFormInputId(DOMAIN_FIELD_IMPORTALIASES, 1, 1)).hasAttribute('disabled')
).toEqual(true);
expect(
document.querySelector('#' + createFormInputId(DOMAIN_FIELD_URL, 1, 1)).hasAttribute('disabled')
).toEqual(true);
expect(
document.querySelector('#' + createFormInputId(DOMAIN_FIELD_URL_TARGET, 1, 1)).hasAttribute('disabled')
).toEqual(true);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { PureComponent, ReactNode } from 'react';
import { List } from 'immutable';

import { HelpLink, URL_ENCODING_TOPIC } from '../../util/helpLinks';

Expand All @@ -9,15 +10,16 @@ import { ONTOLOGY_MODULE_NAME } from '../ontology/actions';
import { hasModule } from '../../app/utils';

import { isFieldFullyLocked } from './propertiesUtil';
import { createFormInputId, createFormInputName } from './utils';
import { createFormInputId, createFormInputName, isEmptyString } from './utils';
import {
DOMAIN_FIELD_DESCRIPTION,
DOMAIN_FIELD_IMPORTALIASES,
DOMAIN_FIELD_LABEL,
DOMAIN_FIELD_ONTOLOGY_PRINCIPAL_CONCEPT,
DOMAIN_FIELD_URL,
DOMAIN_FIELD_URL_TARGET,
} from './constants';
import { DomainField, IDomainFormDisplayOptions } from './models';
import { DomainField, IDomainFormDisplayOptions, IFieldChange } from './models';
import { SectionHeading } from './SectionHeading';
import { DomainFieldLabel } from './DomainFieldLabel';

Expand All @@ -28,6 +30,7 @@ interface NameAndLinkingProps {
field: DomainField;
index: number;
onChange: (string, any) => void;
onMultiChange: (changes: List<IFieldChange>) => void;
}

export class NameAndLinkingOptions extends PureComponent<NameAndLinkingProps> {
Expand All @@ -36,7 +39,30 @@ export class NameAndLinkingOptions extends PureComponent<NameAndLinkingProps> {
};

onChange = (id: string, value: any): void => {
this.props?.onChange(id, value);
this.props.onChange(id, value);
};

handleURLChange = (evt: any): void => {
const { index, domainIndex } = this.props;
const val = evt.target.value;
const isEmpty = isEmptyString(val);

// make sure to uncheck the "open in new tab" option if URL is cleared out
if (isEmpty) {
let changes = List<IFieldChange>();
changes = changes.push({ id: evt.target.id, value: null });
changes = changes.push({
id: createFormInputId(DOMAIN_FIELD_URL_TARGET, domainIndex, index),
value: false,
});
this.props.onMultiChange(changes);
} else {
this.onChange(evt.target.id, isEmpty ? null : val);
}
};

handleURLTargetChange = (evt: any): void => {
this.onChange(evt.target.id, evt.target.checked);
};

getImportAliasHelpText = (): ReactNode => {
Expand Down Expand Up @@ -70,78 +96,91 @@ export class NameAndLinkingOptions extends PureComponent<NameAndLinkingProps> {
<div>
<div className="row">
<div className="col-xs-12">
<SectionHeading title="Name and Linking Options" cls="domain-field-section-hdr" />
<SectionHeading cls="domain-field-section-hdr" title="Name and Linking Options" />
</div>
</div>
<div className="row">
<div className="col-xs-5">
<div className="domain-field-label">Description</div>
<textarea
className="form-control"
rows={4}
value={field.description || ''}
disabled={isFieldFullyLocked(field.lockType)}
id={createFormInputId(DOMAIN_FIELD_DESCRIPTION, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_DESCRIPTION)}
onChange={this.handleChange}
disabled={isFieldFullyLocked(field.lockType)}
rows={4}
value={field.description || ''}
/>
</div>
<div className="col-xs-3">
<div className="domain-field-label">Label</div>
<input
className="form-control"
type="text"
value={field.label || ''}
disabled={isFieldFullyLocked(field.lockType)}
id={createFormInputId(DOMAIN_FIELD_LABEL, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_LABEL)}
onChange={this.handleChange}
disabled={isFieldFullyLocked(field.lockType)}
type="text"
value={field.label || ''}
/>
{!field.isUniqueIdField() &&
!field.isCalculatedField() &&
!domainFormDisplayOptions?.hideImportAliases && (
<>
<div className="domain-field-label">
<DomainFieldLabel
label="Import Aliases"
helpTipBody={this.getImportAliasHelpText()}
label="Import Aliases"
/>
</div>
<input
className="form-control"
type="text"
value={field.importAliases || ''}
disabled={isFieldFullyLocked(field.lockType)}
id={createFormInputId(DOMAIN_FIELD_IMPORTALIASES, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_IMPORTALIASES)}
onChange={this.handleChange}
disabled={isFieldFullyLocked(field.lockType)}
type="text"
value={field.importAliases || ''}
/>
</>
)}
</div>
<div className="col-xs-4">
<div className="domain-field-label">
<DomainFieldLabel label="URL" helpTipBody={this.getURLHelpText()} />
</div>
<input
className="form-control"
type="text"
value={field.URL || ''}
id={createFormInputId(DOMAIN_FIELD_URL, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_URL)}
onChange={this.handleChange}
disabled={isFieldFullyLocked(field.lockType)}
/>
{!appPropertiesOnly &&
hasModule(ONTOLOGY_MODULE_NAME) &&
!field.isUniqueIdField() &&
!field.isCalculatedField() && (
<OntologyConceptAnnotation
id={createFormInputId(DOMAIN_FIELD_ONTOLOGY_PRINCIPAL_CONCEPT, domainIndex, index)}
field={field}
id={createFormInputId(DOMAIN_FIELD_ONTOLOGY_PRINCIPAL_CONCEPT, domainIndex, index)}
onChange={this.onChange}
/>
)}
<div className="domain-field-label">
<DomainFieldLabel helpTipBody={this.getURLHelpText()} label="URL" />
</div>
<input
className="form-control"
disabled={isFieldFullyLocked(field.lockType)}
id={createFormInputId(DOMAIN_FIELD_URL, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_URL)}
onChange={this.handleURLChange}
type="text"
value={field.URL || ''}
/>
{/*GitHub Issue 503: Field editor URL option to set target window (i.e. _blank)*/}
<div className="domain-text-options-col">
<input
checked={field.isTargetBlank}
className="form-control domain-text-option-istargetblank"
disabled={isFieldFullyLocked(field.lockType) || isEmptyString(field.URL)}
id={createFormInputId(DOMAIN_FIELD_URL_TARGET, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_URL_TARGET)}
onChange={this.handleURLTargetChange}
type="checkbox"
/>
<span>Open links in a new tab</span>
</div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,20 @@ exports[`NameAndLinkingOptions Name and Linking options 1`] = `
type="text"
value="This is a URL"
/>
<div
class="domain-text-options-col"
>
<input
checked=""
class="form-control domain-text-option-istargetblank"
id="domainpropertiesrow-isTargetBlank-1-1"
name="domainpropertiesrow-isTargetBlank"
type="checkbox"
/>
<span>
Open links in a new tab
</span>
</div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const DOMAIN_FIELD_DESCRIPTION = 'description';
export const DOMAIN_FIELD_LABEL = 'label';
export const DOMAIN_FIELD_IMPORTALIASES = 'importAliases';
export const DOMAIN_FIELD_URL = 'URL';
export const DOMAIN_FIELD_URL_TARGET = 'isTargetBlank';
export const DOMAIN_FIELD_LOOKUP_CONTAINER = 'lookupContainer';
export const DOMAIN_FIELD_LOOKUP_QUERY = 'lookupQueryValue';
export const DOMAIN_FIELD_LOOKUP_SCHEMA = 'lookupSchema';
Expand Down
Loading