diff --git a/apis/nucleus/src/components/listbox/ListBoxInline.jsx b/apis/nucleus/src/components/listbox/ListBoxInline.jsx
index fd7593d96..6341bf699 100644
--- a/apis/nucleus/src/components/listbox/ListBoxInline.jsx
+++ b/apis/nucleus/src/components/listbox/ListBoxInline.jsx
@@ -21,50 +21,59 @@ import InstanceContext from '../../contexts/InstanceContext';
import ListBoxSearch from './ListBoxSearch';
import useObjectSelections from '../../hooks/useObjectSelections';
-export default function ListBoxPortal({ app, fieldName, stateName, element, options }) {
+export default function ListBoxPortal({ app, fieldIdentifier, stateName, element, options }) {
return ReactDOM.createPortal(
- ,
+ ,
element
);
}
-export function ListBoxInline({ app, fieldName, stateName = '$', options = {} }) {
- const theme = useTheme();
+export function ListBoxInline({ app, fieldIdentifier, stateName = '$', options = {} }) {
const { title, direction, listLayout, search = true } = options;
- const [model] = useSessionModel(
- {
- qInfo: {
- qType: 'njsListbox',
- },
- qListObjectDef: {
- qStateName: stateName,
- qShowAlternatives: true,
- qInitialDataFetch: [
+ const listdef = {
+ qInfo: {
+ qType: 'njsListbox',
+ },
+ qListObjectDef: {
+ qStateName: stateName,
+ qShowAlternatives: true,
+ qInitialDataFetch: [
+ {
+ qTop: 0,
+ qLeft: 0,
+ qWidth: 0,
+ qHeight: 0,
+ },
+ ],
+ qDef: {
+ qSortCriterias: [
{
- qTop: 0,
- qLeft: 0,
- qWidth: 0,
- qHeight: 0,
+ qSortByState: 1,
+ qSortByAscii: 1,
+ qSortByNumeric: 1,
+ qSortByLoadOrder: 1,
},
],
- qDef: {
- qSortCriterias: [
- {
- qSortByState: 1,
- qSortByAscii: 1,
- qSortByNumeric: 1,
- qSortByLoadOrder: 1,
- },
- ],
- qFieldDefs: [fieldName],
- },
},
- title,
},
- app,
- fieldName,
- stateName
- );
+ title,
+ };
+
+ let fieldName;
+ let isMaster = false;
+
+ // Something something lib dimension
+ if (fieldIdentifier.qLibraryId) {
+ listdef.qListObjectDef.qLibraryId = fieldIdentifier.qLibraryId;
+ fieldName = fieldIdentifier.qLibraryId;
+ isMaster = true;
+ } else {
+ listdef.qListObjectDef.qDef.qFieldDefs = [fieldIdentifier];
+ fieldName = fieldIdentifier;
+ }
+
+ const theme = useTheme();
+ const [model] = useSessionModel(listdef, app, fieldName, stateName);
const lock = useCallback(() => {
model.lock('/qListObjectDef');
@@ -137,7 +146,7 @@ export function ListBoxInline({ app, fieldName, stateName = '$', options = {} })
{showTitle && (
- {layout.title || fieldName}
+ {!isMaster ? layout.title || fieldName : layout.qListObject.qDimensionInfo.qFallbackTitle}
)}
diff --git a/apis/nucleus/src/components/listbox/__tests__/list-box-column.spec.jsx b/apis/nucleus/src/components/listbox/__tests__/list-box-column.spec.jsx
new file mode 100644
index 000000000..553f1694f
--- /dev/null
+++ b/apis/nucleus/src/components/listbox/__tests__/list-box-column.spec.jsx
@@ -0,0 +1,320 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { Grid, Typography } from '@material-ui/core';
+import Lock from '@nebula.js/ui/icons/lock';
+
+const [{ default: ListBoxColumn }] = aw.mock(
+ [
+ [
+ require.resolve('@nebula.js/ui/theme'),
+ () => ({
+ makeStyles: () => () => ({
+ S: 'selected',
+ A: 'alternative',
+ X: 'excluded',
+ highlighted: 'highlighted',
+ }),
+ }),
+ ],
+ ],
+ ['../ListBoxRow']
+);
+
+describe('', () => {
+ it('should have default props', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+
+ const type = testInstance.findByType(Grid);
+ expect(type.props.container).to.equal(true);
+ expect(type.props.spacing).to.equal(0);
+ expect(type.props.style).to.deep.equal({});
+ expect(type.props.role).to.equal('row');
+ expect(type.props.className).to.equal('');
+ expect(type.props.onClick.callCount).to.equal(0);
+
+ const types = testInstance.findAllByType(Typography);
+ expect(types).to.have.length(1);
+ expect(types[0].props.component).to.equal('span');
+ expect(types[0].props.noWrap).to.equal(true);
+ expect(types[0].props.children).to.equal('');
+ });
+ it('should set locked state', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: 'L',
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+
+ const type = testInstance.findByType(Lock);
+ expect(type.props.size).to.equal('small');
+ });
+ it('should set selected', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: 'L',
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+ const type = testInstance.findByType(Grid);
+ expect(type.props.className).to.equal('selected');
+ });
+ it('should set alternative', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: 'A',
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+ const type = testInstance.findByType(Grid);
+ expect(type.props.className).to.equal('alternative');
+ });
+ it('should set excluded - qState X', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: 'X',
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+ const type = testInstance.findByType(Grid);
+ expect(type.props.className).to.equal('excluded');
+ });
+ it('should set excluded - qState XS', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: 'XS',
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+ const type = testInstance.findByType(Grid);
+ expect(type.props.className).to.equal('excluded');
+ });
+ it('should set excluded - qState XL', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: 'XL',
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+ const type = testInstance.findByType(Grid);
+ expect(type.props.className).to.equal('excluded');
+ });
+ it('should highlight ranges', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: '',
+ qText: 'nebula.js ftw',
+ qHighlightRanges: {
+ qRanges: [{ qCharPos: 0, qCharCount: 9 }],
+ },
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+ const types = testInstance.findAllByType(Typography);
+ expect(types[0].props.children).to.equal('nebula.js');
+ expect(types[0].props.className).to.equal('highlighted');
+ expect(types[1].props.children).to.equal(' ftw');
+ });
+ it('should highlight ranges', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: '',
+ qText: 'nebula.js ftw',
+ qHighlightRanges: {
+ qRanges: [{ qCharPos: 10, qCharCount: 3 }],
+ },
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+ const types = testInstance.findAllByType(Typography);
+ expect(types[0].props.children).to.equal('nebula.js ');
+ expect(types[1].props.children).to.equal('ftw');
+ expect(types[1].props.className).to.equal('highlighted');
+ });
+ it('should highlight ranges', () => {
+ const index = 0;
+ const style = {};
+ const data = {
+ onClick: sinon.spy(),
+ pages: [
+ {
+ qArea: {
+ qLeft: 0,
+ qTop: 0,
+ qWidth: 0,
+ qHeight: 100,
+ },
+ qMatrix: [
+ [
+ {
+ qState: '',
+ qText: 'nebula.js ftw yeah buddy',
+ qHighlightRanges: {
+ qRanges: [{ qCharPos: 14, qCharCount: 4 }],
+ },
+ },
+ ],
+ ],
+ },
+ ],
+ };
+ const testRenderer = renderer.create();
+ const testInstance = testRenderer.root;
+ const types = testInstance.findAllByType(Typography);
+ expect(types[0].props.children).to.equal('nebula.js ftw ');
+ expect(types[1].props.children).to.equal('yeah');
+ expect(types[1].props.className).to.equal('highlighted');
+ expect(types[2].props.children).to.equal(' buddy');
+ });
+});
diff --git a/apis/nucleus/src/index.js b/apis/nucleus/src/index.js
index 7d8db8a0b..d11ba6cc4 100644
--- a/apis/nucleus/src/index.js
+++ b/apis/nucleus/src/index.js
@@ -311,19 +311,24 @@ function nuked(configuration = {}) {
return selectionsApi;
},
/**
- * Gets the instance of the specified field
- * @param {string} fieldName
- * @returns {Promise}
+ * Gets the listbox instance of the specified field
+ * @param {string|LibraryField} fieldIdentifier Fieldname as a string or a Library dimension
+ * @returns {Promise}
* @experimental
* @example
- * const field = await n.field("MyField");
- * field.mount(element, { title: "Hello Field"});
+ * const listbox = await n.listbox("MyField");
+ * listbox.mount(element, { title: "Hello Field"});
*/
- field: async (fieldName) => {
+ listbox: async (fieldIdentifier) => {
+ const fieldName = typeof fieldIdentifier === 'string' ? fieldIdentifier : fieldIdentifier.qLibraryId;
+ if (!fieldName) {
+ throw new Error(`Field identifier must be provided`);
+ }
+
/**
* @class
* @hideconstructor
- * @alias FieldSelections
+ * @alias ListboxInstance
* @experimental
*/
const fieldSels = {
@@ -338,7 +343,7 @@ function nuked(configuration = {}) {
* @param {boolean=} [options.search=true] To show the search bar
* @experimental
* @example
- * field.mount(element);
+ * listbox.mount(element);
*/
mount(element, options = {}) {
if (!element) {
@@ -350,7 +355,7 @@ function nuked(configuration = {}) {
this._instance = ListBoxPortal({
element,
app,
- fieldName,
+ fieldIdentifier,
options,
});
root.add(this._instance);
@@ -359,7 +364,7 @@ function nuked(configuration = {}) {
* Unmounts the field listbox from the DOM.
* @experimental
* @example
- * field.unmount();
+ * listbox.unmount();
*/
unmount() {
if (this._instance) {
diff --git a/apis/nucleus/src/object/create-session-object.js b/apis/nucleus/src/object/create-session-object.js
index 7ad8d0895..ba552b8be 100644
--- a/apis/nucleus/src/object/create-session-object.js
+++ b/apis/nucleus/src/object/create-session-object.js
@@ -1,6 +1,11 @@
import populateData from './populator';
import init from './initiate';
import { subscribe, modelStore } from '../stores/model-store';
+
+/**
+ * @typedef {string | qae.NxDimension | qae.NxMeasure | LibraryField} Field
+ */
+
/**
* @interface CreateConfig
* @description Rendering configuration for creating and rendering a new object
@@ -10,7 +15,6 @@ import { subscribe, modelStore } from '../stores/model-store';
* @property {(Field[])=} fields
* @property {qae.GenericObjectProperties=} properties
*/
-
export default async function createSessionObject({ type, version, fields, properties, options, element }, halo) {
let mergedProps = {};
let error;
diff --git a/apis/nucleus/src/object/populator.js b/apis/nucleus/src/object/populator.js
index 64e1a2111..d0b96a8d1 100644
--- a/apis/nucleus/src/object/populator.js
+++ b/apis/nucleus/src/object/populator.js
@@ -1,9 +1,5 @@
import hcHandler from './hc-handler';
-/**
- * @typedef {(string | qae.NxDimension | qae.NxMeasure | LibraryField)} Field
- */
-
/**
* @interface LibraryField
* @property {string} qLibraryId
diff --git a/apis/stardust/api-spec/spec.json b/apis/stardust/api-spec/spec.json
index 9103e268a..1e4eba1de 100644
--- a/apis/stardust/api-spec/spec.json
+++ b/apis/stardust/api-spec/spec.json
@@ -605,7 +605,7 @@
"optional": true,
"kind": "array",
"items": {
- "type": "Field"
+ "type": "#/definitions/Field"
}
},
"properties": {
@@ -688,25 +688,37 @@
"// limit constraints\nn.context({ constraints: { active: true } });"
]
},
- "field": {
- "description": "Gets the instance of the specified field",
+ "listbox": {
+ "description": "Gets the listbox instance of the specified field",
"stability": "experimental",
"kind": "function",
"params": [
{
- "name": "fieldName",
- "type": "string"
+ "name": "fieldIdentifier",
+ "description": "Fieldname as a string or a Library dimension",
+ "kind": "union",
+ "items": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "#/definitions/LibraryField"
+ }
+ ],
+ "type": "any"
}
],
"returns": {
"type": "Promise",
"generics": [
{
- "type": "#/definitions/FieldSelections"
+ "type": "#/definitions/ListboxInstance"
}
]
},
- "examples": ["const field = await n.field(\"MyField\");\nfield.mount(element, { title: \"Hello Field\"});"]
+ "examples": [
+ "const listbox = await n.listbox(\"MyField\");\nlistbox.mount(element, { title: \"Hello Field\"});"
+ ]
},
"render": {
"description": "Renders a visualization into an HTMLElement.",
@@ -834,66 +846,23 @@
"type": "#/definitions/ExportFormat"
}
},
- "FieldSelections": {
- "stability": "experimental",
- "kind": "class",
- "constructor": {
- "kind": "function",
- "params": []
- },
- "entries": {},
- "staticEntries": {
- "mount": {
- "description": "Mounts the field as a listbox into the provided HTMLElement.",
- "stability": "experimental",
- "kind": "function",
- "params": [
- {
- "name": "element",
- "type": "HTMLElement"
- },
- {
- "name": "options",
- "description": "Settings for the embedded listbox",
- "optional": true,
- "kind": "object",
- "entries": {
- "title": {
- "description": "Custom title, defaults to fieldname",
- "optional": true,
- "type": "string"
- },
- "direction": {
- "description": "Direction setting ltr|rtl.",
- "optional": true,
- "defaultValue": "ltr",
- "type": "string"
- },
- "listLayout": {
- "description": "Layout direction vertical|horizontal",
- "optional": true,
- "defaultValue": "vertical",
- "type": "string"
- },
- "search": {
- "description": "To show the search bar",
- "optional": true,
- "defaultValue": true,
- "type": "boolean"
- }
- }
- }
- ],
- "examples": ["field.mount(element);"]
+ "Field": {
+ "kind": "union",
+ "items": [
+ {
+ "type": "string"
},
- "unmount": {
- "description": "Unmounts the field listbox from the DOM.",
- "stability": "experimental",
- "kind": "function",
- "params": [],
- "examples": ["field.unmount();"]
+ {
+ "type": "qae.NxDimension"
+ },
+ {
+ "type": "qae.NxMeasure"
+ },
+ {
+ "type": "#/definitions/LibraryField"
}
- }
+ ],
+ "type": "any"
},
"FieldTarget": {
"kind": "interface",
@@ -1036,6 +1005,67 @@
}
}
},
+ "ListboxInstance": {
+ "stability": "experimental",
+ "kind": "class",
+ "constructor": {
+ "kind": "function",
+ "params": []
+ },
+ "entries": {},
+ "staticEntries": {
+ "mount": {
+ "description": "Mounts the field as a listbox into the provided HTMLElement.",
+ "stability": "experimental",
+ "kind": "function",
+ "params": [
+ {
+ "name": "element",
+ "type": "HTMLElement"
+ },
+ {
+ "name": "options",
+ "description": "Settings for the embedded listbox",
+ "optional": true,
+ "kind": "object",
+ "entries": {
+ "title": {
+ "description": "Custom title, defaults to fieldname",
+ "optional": true,
+ "type": "string"
+ },
+ "direction": {
+ "description": "Direction setting ltr|rtl.",
+ "optional": true,
+ "defaultValue": "ltr",
+ "type": "string"
+ },
+ "listLayout": {
+ "description": "Layout direction vertical|horizontal",
+ "optional": true,
+ "defaultValue": "vertical",
+ "type": "string"
+ },
+ "search": {
+ "description": "To show the search bar",
+ "optional": true,
+ "defaultValue": true,
+ "type": "boolean"
+ }
+ }
+ }
+ ],
+ "examples": ["listbox.mount(element);"]
+ },
+ "unmount": {
+ "description": "Unmounts the field listbox from the DOM.",
+ "stability": "experimental",
+ "kind": "function",
+ "params": [],
+ "examples": ["listbox.unmount();"]
+ }
+ }
+ },
"LoadType": {
"kind": "interface",
"params": [