Skip to content

Commit 1fa3e15

Browse files
authored
is743/service selection (#775)
Each service card shows: title, description and author/contact Auto focus in the Filter textfield when opening the view When focus is on filter, ENTER key adds the first result Enter key stroke automatically adds Tab key moves the selected service Sorting of the entries (alphabetically)
1 parent a07cc64 commit 1fa3e15

File tree

17 files changed

+633
-149
lines changed

17 files changed

+633
-149
lines changed

services/web/client/package-lock.json

Lines changed: 7 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/web/client/source/class/qxapp/component/filter/MFilterable.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ qx.Mixin.define("qxapp.component.filter.MFilterable", {
2525
*
2626
* @param {String} groupId Id of the filter group to subscribe to.
2727
*/
28-
_subscribeToFilterGroup: function(groupId) {
28+
subscribeToFilterGroup: function(groupId) {
2929
const msgName = qxapp.utils.Utils.capitalize(groupId, "filter");
3030
qx.event.message.Bus.getInstance().subscribe(msgName, this.__subscriber, this);
3131
},
3232
/**
33-
* Subscriber function for incoming messages.
33+
* Subscriber function for incoming messages. It implements the common filtering workflow of every
34+
* filterable GUI element: If the filter state is appropiate, compare it with the own state and act
35+
* accordingly by applying the filter or removing it.
3436
*
3537
* @param {qx.event.message.Message} msg Message dispatched.
3638
*/

services/web/client/source/class/qxapp/component/filter/UIFilter.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ qx.Class.define("qxapp.component.filter.UIFilter", {
6767
* @param {Object} data Raw data coming from the filter.
6868
*/
6969
_filterChange: function(data) {
70-
const msgData = {
70+
const filterData = {
7171
groupId: this.getGroupId(),
7272
filterId: this.getFilterId(),
7373
data
7474
};
75-
qx.event.message.Bus.getInstance().dispatchByName(this._getMessageName(), msgData);
75+
qxapp.component.filter.UIFilterController.getInstance().publish(filterData);
7676
}
7777
}
7878
});

services/web/client/source/class/qxapp/component/filter/UIFilterController.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ qx.Class.define("qxapp.component.filter.UIFilterController", {
5454
registerFilter: function(filter) {
5555
const filterId = filter.getFilterId();
5656
const groupId = filter.getGroupId();
57-
qx.event.message.Bus.getInstance().subscribe(this.__getInputMessageName(filterId, groupId), this.__subscriber, this);
5857
// Store filter reference for managing
5958
this.__filters = this.__filters || {};
6059
this.__filters[groupId] = this.__filters[groupId] || {};
@@ -105,13 +104,21 @@ qx.Class.define("qxapp.component.filter.UIFilterController", {
105104
return qxapp.utils.Utils.capitalize(groupId, suffix);
106105
},
107106

108-
__subscriber: function(msg) {
107+
/**
108+
* Function called when a filter state changes and it wants to publish those changes to trigger the filtering.
109+
*
110+
* @param {Object} filterData Mandatory data coming from the filter.
111+
* @param {String} filterData.groupId Group id of the filter that changed.
112+
* @param {String} filterData.filterId Filter id of the filter that changed.
113+
* @param {Object} filterData.data Data contained by the filter.
114+
*/
115+
publish: function(filterData) {
109116
// Update state
110117
const {
111118
groupId,
112119
filterId,
113120
data
114-
} = msg.getData();
121+
} = filterData;
115122
this.__state = this.__state || {};
116123
this.__state[groupId] = this.__state[groupId] || {};
117124
this.__state[groupId][filterId] = data;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* ************************************************************************
2+
3+
qxapp - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2019 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Ignacio Pascual (ignapas)
15+
16+
************************************************************************ */
17+
18+
/**
19+
* This is a view to display the available services in a flowing fashion. Creates a ServiceJumbo button
20+
* for every service in the model and subscribes it to the filter group.
21+
*/
22+
qx.Class.define("qxapp.component.service.ServiceBrowser", {
23+
extend: qx.ui.core.Widget,
24+
25+
/**
26+
* If the optional parameter is given, the elements will be subscribed to the filter group of the given id.
27+
*
28+
* @param {String} [groupId] Id of the filter group the service Jumbo buttons will be subscribed to.
29+
*/
30+
construct: function(groupId) {
31+
this.base(arguments);
32+
this._setLayout(new qx.ui.layout.Flow(5, 5));
33+
if (groupId) {
34+
this.__filterGroup = groupId;
35+
}
36+
},
37+
38+
events: {
39+
"changeValue": "qx.event.type.Data",
40+
"serviceadd": "qx.event.type.Data"
41+
},
42+
43+
properties: {
44+
appearance: {
45+
refine: true,
46+
init: "service-browser"
47+
},
48+
model: {
49+
nullable: true,
50+
check: "qx.data.Array",
51+
apply: "_applyModel"
52+
}
53+
},
54+
55+
members: {
56+
__buttonGroup: null,
57+
__filterGroup: null,
58+
59+
_applyModel: function(model) {
60+
this._removeAll();
61+
const group = this.__buttonGroup = new qx.ui.form.RadioGroup().set({
62+
allowEmptySelection: true
63+
});
64+
model.toArray()
65+
.sort((a, b) => a.getName().localeCompare(b.getName()))
66+
.forEach(service => {
67+
const button = new qxapp.component.service.ServiceJumbo(service);
68+
if (this.__filterGroup !== null) {
69+
button.subscribeToFilterGroup(this.__filterGroup);
70+
}
71+
group.add(button);
72+
this._add(button);
73+
button.addListener("dbltap", e => {
74+
this.fireDataEvent("serviceadd", button.getServiceModel());
75+
}, this);
76+
button.addListener("keypress", e => {
77+
if (e.getKeyIdentifier() === "Enter") {
78+
this.fireDataEvent("serviceadd", button.getServiceModel());
79+
}
80+
}, this);
81+
});
82+
group.addListener("changeValue", e => this.dispatchEvent(e.clone()), this);
83+
},
84+
85+
/**
86+
* Public function to get the currently selected service.
87+
*
88+
* @return Returns the model of the selected service or null if selection is empty.
89+
*/
90+
getSelected: function() {
91+
if (this.__buttonGroup && this.__buttonGroup.getSelection().length) {
92+
return this.__buttonGroup.getSelection()[0].getServiceModel();
93+
}
94+
return null;
95+
},
96+
97+
/**
98+
* Function checking if the selection is empty or not
99+
*
100+
* @return True if no item is selected, false if there one or more item selected.
101+
*/
102+
isSelectionEmpty: function() {
103+
if (this.__buttonGroup == null) { // eslint-disable-line no-eq-null
104+
return true;
105+
}
106+
return this.__buttonGroup.getSelection().length === 0;
107+
},
108+
109+
/**
110+
* Function that selects the first visible button.
111+
*/
112+
selectFirstVisible: function() {
113+
if (this._hasChildren()) {
114+
const buttons = this._getChildren();
115+
let current = buttons[0];
116+
let i = 1;
117+
while (i<buttons.length && !current.isVisible()) {
118+
current = buttons[i++];
119+
}
120+
if (current.isVisible()) {
121+
this.__buttonGroup.setSelection([this._getChildren()[i-1]]);
122+
}
123+
}
124+
}
125+
}
126+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* ************************************************************************
2+
3+
qxapp - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2019 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Ignacio Pascual (ignapas)
15+
16+
************************************************************************ */
17+
18+
/**
19+
* Big button representing a service. It shows it name, description and contact information. It also adds
20+
* filtering capabilities.
21+
*/
22+
qx.Class.define("qxapp.component.service.ServiceJumbo", {
23+
extend: qxapp.ui.form.Jumbo,
24+
include: qxapp.component.filter.MFilterable,
25+
implement: qxapp.component.filter.IFilterable,
26+
27+
/**
28+
* Constructor
29+
*/
30+
construct: function(serviceModel, icon) {
31+
this.base(arguments, serviceModel.getName(), serviceModel.getDescription(), icon, serviceModel.getContact());
32+
if (serviceModel != null) { // eslint-disable-line no-eq-null
33+
this.setServiceModel(serviceModel);
34+
}
35+
},
36+
37+
properties: {
38+
serviceModel: {}
39+
},
40+
41+
members: {
42+
_filter: function() {
43+
this.exclude();
44+
},
45+
46+
_unfilter: function() {
47+
this.show();
48+
},
49+
50+
_shouldApplyFilter: function(data) {
51+
if (data.text) {
52+
const label = this.getServiceModel().getName()
53+
.trim()
54+
.toLowerCase();
55+
if (label.indexOf(data.text) === -1) {
56+
return true;
57+
}
58+
}
59+
if (data.tags && data.tags.length) {
60+
const category = this.getServiceModel().getCategory() || "";
61+
const type = this.getServiceModel().getType() || "";
62+
if (!data.tags.includes(category.trim().toLowerCase()) && !data.tags.includes(type.trim().toLowerCase())) {
63+
return true;
64+
}
65+
}
66+
return false;
67+
},
68+
69+
_shouldReactToFilter: function(data) {
70+
if (data.text && data.text.length > 1) {
71+
return true;
72+
}
73+
if (data.tags && data.tags.length) {
74+
return true;
75+
}
76+
return false;
77+
}
78+
}
79+
});

services/web/client/source/class/qxapp/component/widget/NodesTree.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ qx.Class.define("qxapp.component.widget.NodesTree", {
103103
const iconSize = 16;
104104
let toolbar = this.__toolBar = new qx.ui.toolbar.ToolBar();
105105

106-
let newButton = new qx.ui.toolbar.Button("New", "@FontAwesome5Solid/plus/"+iconSize);
106+
let newButton = new qx.ui.toolbar.Button("New", "@FontAwesome5Solid/plus/"+iconSize, new qx.ui.command.Command("Ctrl+N"));
107107
newButton.addListener("execute", e => {
108108
this.__addNode();
109109
}, this);

services/web/client/source/class/qxapp/component/workbench/EdgeUI.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ qx.Class.define("qxapp.component.workbench.EdgeUI", {
4444
this.setEdge(edge);
4545
this.setRepresentation(representation);
4646

47-
this._subscribeToFilterGroup("workbench");
47+
this.subscribeToFilterGroup("workbench");
4848
},
4949

5050
events: {

0 commit comments

Comments
 (0)