Skip to content

Commit

Permalink
feat(popup-menu): add API to provide custom no search results entry
Browse files Browse the repository at this point in the history
  • Loading branch information
philippfromme committed Jan 18, 2024
1 parent 0ec3bb3 commit 45904e0
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 36 deletions.
22 changes: 21 additions & 1 deletion lib/features/popup-menu/PopupMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ PopupMenu.prototype._render = function() {
className,
entries,
headerEntries,
noSearchResultsCallback,
options
} = this._current;

Expand Down Expand Up @@ -133,6 +134,7 @@ PopupMenu.prototype._render = function() {
className=${ className }
entries=${ entriesArray }
headerEntries=${ headerEntriesArray }
noSearchResultsCallback=${ noSearchResultsCallback }
scale=${ scale }
onOpened=${ this._onOpened.bind(this) }
onClosed=${ this._onClosed.bind(this) }
Expand Down Expand Up @@ -171,7 +173,8 @@ PopupMenu.prototype.open = function(target, providerId, position, options) {

const {
entries,
headerEntries
headerEntries,
noSearchResultsCallback
} = this._getContext(target, providerId);

this._current = {
Expand All @@ -180,6 +183,7 @@ PopupMenu.prototype.open = function(target, providerId, position, options) {
target,
entries,
headerEntries,
noSearchResultsCallback,
container: this._createContainer({ provider: providerId }),
options
};
Expand All @@ -204,9 +208,12 @@ PopupMenu.prototype._getContext = function(target, provider) {

const headerEntries = this._getHeaderEntries(target, providers);

const noSearchResultsCallback = this._getNoSearchResultsCallback(providers);

return {
entries,
headerEntries,
noSearchResultsCallback,
empty: !(
Object.keys(entries).length ||
Object.keys(headerEntries).length
Expand Down Expand Up @@ -509,6 +516,19 @@ PopupMenu.prototype._getHeaderEntries = function(target, providers) {
};


PopupMenu.prototype._getNoSearchResultsCallback = function(providers) {
var noSearchResultsCallback;

forEach(providers, function(provider) {
if (isFunction(provider.getNoSearchResultsCallback)) {
noSearchResultsCallback = provider.getNoSearchResultsCallback();
}
});

return noSearchResultsCallback;
};


/**
* Check if the popup menu is open.
*
Expand Down
5 changes: 4 additions & 1 deletion lib/features/popup-menu/PopupMenuComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { isDefined } from 'min-dash';
/**
* @typedef {import('./PopupMenuProvider').PopupMenuEntry} PopupMenuEntry
* @typedef {import('./PopupMenuProvider').PopupMenuHeaderEntry} PopupMenuHeaderEntry
* @typedef {import('./PopupMenuProvider').PopupMenuNoSearchResultsCallback} PopupMenuNoSearchResultsCallback
*
* @typedef {import('../../util/Types').Point} Point
*/
Expand All @@ -36,6 +37,7 @@ import { isDefined } from 'min-dash';
* @param {number} props.scale
* @param {string} [props.title]
* @param {boolean} [props.search]
* @param {PopupMenuNoSearchResultsCallback} [props.noSearchResultsCallback]
* @param {number} [props.width]
*/
export default function PopupMenuComponent(props) {
Expand All @@ -49,6 +51,7 @@ export default function PopupMenuComponent(props) {
width,
scale,
search,
noSearchResultsCallback,
entries: originalEntries,
onOpened,
onClosed
Expand Down Expand Up @@ -246,7 +249,7 @@ export default function PopupMenuComponent(props) {
/>
</div>
${ entries.length === 0 && html`
<div class="djs-popup-no-results">No matching entries found.</div>
<div class="djs-popup-no-results">${ noSearchResultsCallback ? noSearchResultsCallback(value) : 'No matching entries found.' }</div>
` }
` }
</${PopupMenuWrapper}>
Expand Down
17 changes: 17 additions & 0 deletions lib/features/popup-menu/PopupMenuProvider.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { VNode } from '@bpmn-io/diagram-js-ui';

import { PopupMenuTarget } from './PopupMenu';

export type PopupMenuEntryAction = (event: Event, entry: PopupMenuEntry, action?: string) => any;
Expand Down Expand Up @@ -31,6 +33,8 @@ export type PopupMenuHeaderEntries = PopupMenuHeaderEntry[];

export type PopupMenuProviderHeaderEntriesCallback = (entries: PopupMenuHeaderEntries) => PopupMenuHeaderEntries;

export type PopupMenuNoSearchResultsCallback = (value: string) => VNode;

/**
* An interface to be implemented by a popup menu provider.
*/
Expand Down Expand Up @@ -97,4 +101,17 @@ export default interface PopupMenuProvider {
* @param target
*/
getHeaderEntries?(target: PopupMenuTarget): PopupMenuProviderHeaderEntriesCallback | PopupMenuHeaderEntries;

/**
* Returns a callback that returns a VNode to be rendered when there are no search results.
*
* @example
*
* ```javascript
* getNoSearchResultsCallback() {
* return (value) => No results for <strong>{ value }</strong>;
* }
* ```
*/
getNoSearchResultsCallback?(): PopupMenuNoSearchResultsCallback;
}
94 changes: 60 additions & 34 deletions test/spec/features/popup-menu/PopupMenuSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { createEvent as globalEvent } from '../../../util/MockEvents';
import popupMenuModule from 'lib/features/popup-menu';
import modelingModule from 'lib/features/modeling';

import { html } from 'lib/ui';


describe('features/popup-menu', function() {

Expand Down Expand Up @@ -1218,44 +1220,51 @@ describe('features/popup-menu', function() {
expect(popupMenu.isOpen()).to.be.true;
}));

});

describe('search rank', function() {

var testMenuProvider = {
getEntries: function() {
return [
{
id: 'A',
label: 'A'
},
{
id: 'B',
label: 'B'
},
{
id: 'C',
label: 'C'
},
{
id: 'D',
label: 'D',
rank: 1
},
{
id: 'E',
label: 'E',
rank: 0
},
{
id: 'F',
label: 'F (hide initially)',
rank: -1
}
];
}
};
describe('search', function() {

var testMenuProvider = {
getEntries: function() {
return [
{
id: 'A',
label: 'A'
},
{
id: 'B',
label: 'B'
},
{
id: 'C',
label: 'C'
},
{
id: 'D',
label: 'D',
rank: 1
},
{
id: 'E',
label: 'E',
rank: 0
},
{
id: 'F',
label: 'F (hide initially)',
rank: -1
}
];
},
getNoSearchResultsCallback: () => {
return value => html`<h1 id="no-search-results-foobar">${ value }</h1>`;
}
};


describe('ranking', function() {

it('should hide rank < 0 items', inject(async function(popupMenu) {

// given
Expand Down Expand Up @@ -1288,6 +1297,23 @@ describe('features/popup-menu', function() {

});


it('should render custom entry if no search results', inject(async function(popupMenu) {

// given
popupMenu.registerProvider('test-menu', testMenuProvider);
popupMenu.open({}, 'test-menu', { x: 100, y: 100 }, { search: true });

// when
await triggerSearch('foobar');

// then
var node = queryPopup('#no-search-results-foobar');

expect(node).to.exist;
expect(node.textContent).to.eql('foobar');
}));

});


Expand Down

0 comments on commit 45904e0

Please sign in to comment.