Skip to content

Commit 196a590

Browse files
authored
feat(ui5-barcode-scanner-dialog): Initial implementation (#3523)
* feat(ui5-barcode-scanner-dialog): Initial implementation * feat(ui5-barcode-scanner-dialog): Initial implementation * remove obsolete function argument * corrected jsdoc * adopted show method * correct jsdoc * added check for loading status upon open; addopted show in playground sample
1 parent e4770b3 commit 196a590

10 files changed

+488
-1
lines changed

packages/fiori/bundle.common.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import "./dist/illustrations/BeforeSearch";
1616

1717
// FIORI components
1818
import Bar from "./dist/Bar.js";
19+
import BarcodeScannerDialog from "./dist/BarcodeScannerDialog.js";
1920
import FlexibleColumnLayout from "./dist/FlexibleColumnLayout.js";
2021
import IllustratedMessage from "./dist/IllustratedMessage.js";
2122
import ProductSwitch from "./dist/ProductSwitch.js";

packages/fiori/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
"@ui5/webcomponents-base": "0.34.0",
4545
"@ui5/webcomponents-icons": "1.0.0-rc.14",
4646
"@ui5/webcomponents-ie11": "0.34.0",
47-
"@ui5/webcomponents-theme-base": "1.0.0-rc.14"
47+
"@ui5/webcomponents-theme-base": "1.0.0-rc.14",
48+
"@zxing/library": "^0.18.3"
4849
},
4950
"devDependencies": {
5051
"@ui5/webcomponents-tools": "1.0.0-rc.14",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<ui5-dialog stretch
2+
dir={{effectiveDir}}
3+
@ui5-before-open={{_startReader}}
4+
@ui5-after-close={{_resetReader}}>
5+
<div class="ui5-barcode-scanner-dialog-video-wrapper">
6+
<video class="ui5-barcode-scanner-dialog-video"></video>
7+
</div>
8+
<div slot="footer" class="ui5-barcode-scanner-dialog-footer">
9+
<ui5-button design="Transparent" @click={{_closeDialog}}>{{_cancelButtonText}}</ui5-button>
10+
</div>
11+
<ui5-busy-indicator
12+
?active={{loading}}
13+
size="Large"
14+
text="{{_busyIndicatorText}}"
15+
class="ui5-barcode-scanner-dialog-busy">
16+
</ui5-busy-indicator>
17+
</ui5-dialog>
+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
2+
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
3+
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
4+
import Dialog from "@ui5/webcomponents/dist/Dialog.js";
5+
import Button from "@ui5/webcomponents/dist/Button.js";
6+
import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js";
7+
import { BrowserMultiFormatReader, NotFoundException } from "@zxing/library";
8+
9+
// Template
10+
import BarcodeScannerDialogTemplate from "./generated/templates/BarcodeScannerDialogTemplate.lit.js";
11+
12+
// Styles
13+
import barcodeScannerDialogCss from "./generated/themes/BarcodeScannerDialog.css.js";
14+
15+
// Texts
16+
import {
17+
BARCODE_SCANNER_DIALOG_CANCEL_BUTTON_TXT,
18+
BARCODE_SCANNER_DIALOG_LOADING_TXT,
19+
} from "./generated/i18n/i18n-defaults.js";
20+
21+
const defaultMediaConstraints = {
22+
audio: false,
23+
video: {
24+
height: {
25+
min: 480,
26+
ideal: 960,
27+
max: 1440,
28+
},
29+
aspectRatio: 1.333333333,
30+
facingMode: "environment",
31+
},
32+
};
33+
34+
/**
35+
* @public
36+
*/
37+
const metadata = {
38+
tag: "ui5-barcode-scanner-dialog",
39+
languageAware: true,
40+
slots: /** @lends sap.ui.webcomponents.fiori.BarcodeScannerDialog.prototype */ {
41+
},
42+
properties: /** @lends sap.ui.webcomponents.fiori.BarcodeScannerDialog.prototype */ {
43+
/**
44+
* Indicates whether a loading indicator should be displayed in the dialog.
45+
*
46+
* @type {boolean}
47+
* @defaultvalue false
48+
* @private
49+
*/
50+
loading: {
51+
type: Boolean,
52+
},
53+
},
54+
events: /** @lends sap.ui.webcomponents.fiori.BarcodeScannerDialog.prototype */ {
55+
/**
56+
* Fires when the scan is completed successfuuly.
57+
*
58+
* @event sap.ui.webcomponents.fiori.BarcodeScannerDialog#scan-success
59+
* @param {String} text the scan result as string
60+
* @param {Object} rawBytes the scan result as a Uint8Array
61+
* @public
62+
*/
63+
"scan-success": {
64+
detail: {
65+
text: { type: String },
66+
rawBytes: { type: Object },
67+
},
68+
},
69+
70+
/**
71+
* Fires when the scan fails with error.
72+
*
73+
* @event sap.ui.webcomponents.fiori.BarcodeScannerDialog#scan-error
74+
* @param {String} message the error message
75+
* @public
76+
*/
77+
"scan-error": {
78+
detail: {
79+
message: { type: String },
80+
},
81+
},
82+
},
83+
};
84+
85+
/**
86+
* @class
87+
*
88+
* <h3 class="comment-api-title">Overview</h3>
89+
*
90+
* The <code>BarcodeScannerDialog</code> component provides barcode scanning functionality for all devices that support the <code>MediaDevices.getUserMedia()</code> native API.
91+
* Opening the dialog launches the device camera and scans for known barcode formats.
92+
* <br>
93+
* <br>
94+
* A <code>scanSuccess</code> event fires whenever a barcode is identified
95+
* and a <code>scanError</code> event fires when the scan failed (for example, due to missing permisions).
96+
* <br>
97+
* <br>
98+
* Internally, the component uses the zxing-js/library third party OSS.
99+
*
100+
* For a list of supported barcode formats, see the <ui5-link target="_blank" href="https://github.com/zxing-js/library">zxing-js/library</ui5-link> documentation.
101+
*
102+
* @constructor
103+
* @author SAP SE
104+
* @alias sap.ui.webcomponents.fiori.BarcodeScannerDialog
105+
* @extends UI5Element
106+
* @tagname ui5-barcode-scanner-dialog
107+
* @public
108+
* @since 1.0.0-rc.16
109+
*/
110+
class BarcodeScannerDialog extends UI5Element {
111+
constructor() {
112+
super();
113+
this.i18nBundle = getI18nBundle("@ui5/webcomponents-fiori");
114+
this._codeReader = new BrowserMultiFormatReader();
115+
}
116+
117+
static get metadata() {
118+
return metadata;
119+
}
120+
121+
static get render() {
122+
return litRender;
123+
}
124+
125+
static get template() {
126+
return null;
127+
}
128+
129+
static get staticAreaTemplate() {
130+
return BarcodeScannerDialogTemplate;
131+
}
132+
133+
static get styles() {
134+
return null;
135+
}
136+
137+
static get staticAreaStyles() {
138+
return [barcodeScannerDialogCss];
139+
}
140+
141+
static async onDefine() {
142+
await fetchI18nBundle("@ui5/webcomponents-fiori");
143+
}
144+
145+
/**
146+
* Shows a dialog with the camera videostream. Starts a scan session.
147+
* @public
148+
*/
149+
show() {
150+
if (this.loading) {
151+
console.warn("Barcode scanning is already in progress."); // eslint-disable-line
152+
return;
153+
}
154+
155+
if (!this._hasGetUserMedia()) {
156+
this.fireEvent("scan-error", { message: "getUserMedia() is not supported by your browser" });
157+
return;
158+
}
159+
160+
this.loading = true;
161+
162+
this._getUserPermission()
163+
.then(() => this._showDialog())
164+
.catch(err => {
165+
this.fireEvent("scan-error", { message: err });
166+
this.loading = false;
167+
});
168+
}
169+
170+
/**
171+
* Closes the dialog and the scan session.
172+
* @public
173+
*/
174+
close() {
175+
this._closeDialog();
176+
this.loading = false;
177+
}
178+
179+
/**
180+
* PRIVATE METHODS
181+
*/
182+
183+
_hasGetUserMedia() {
184+
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
185+
}
186+
187+
_getUserPermission() {
188+
return navigator.mediaDevices.getUserMedia(defaultMediaConstraints);
189+
}
190+
191+
async _getDialog() {
192+
const staticAreaItem = await this.getStaticAreaItemDomRef();
193+
return staticAreaItem.querySelector("[ui5-dialog]");
194+
}
195+
196+
async _getVideoElement() {
197+
const staticAreaItem = await this.getStaticAreaItemDomRef();
198+
return staticAreaItem.querySelector(".ui5-barcode-scanner-dialog-video");
199+
}
200+
201+
async _showDialog() {
202+
this.dialog = await this._getDialog();
203+
this.dialog.show();
204+
}
205+
206+
_closeDialog() {
207+
if (this._isOpen) {
208+
this.dialog.close();
209+
}
210+
}
211+
212+
_startReader() {
213+
this._decodeFromCamera(null);
214+
}
215+
216+
async _resetReader() {
217+
const videoElement = await this._getVideoElement();
218+
videoElement.pause();
219+
this._codeReader.reset();
220+
}
221+
222+
async _decodeFromCamera(cameraId) {
223+
const videoElement = await this._getVideoElement();
224+
225+
this._codeReader.decodeFromVideoDevice(cameraId, videoElement, (result, err) => {
226+
this.loading = false;
227+
if (result) {
228+
this.fireEvent("scan-success",
229+
{
230+
text: result.getText(),
231+
rawBytes: result.getRawBytes(),
232+
});
233+
}
234+
if (err && !(err instanceof NotFoundException)) {
235+
this.fireEvent("scan-error", { message: err });
236+
}
237+
}).catch(err => this.fireEvent("scan-error", { message: err }));
238+
}
239+
240+
get _isOpen() {
241+
return !!this.dialog && this.dialog.opened;
242+
}
243+
244+
get _cancelButtonText() {
245+
return this.i18nBundle.getText(BARCODE_SCANNER_DIALOG_CANCEL_BUTTON_TXT);
246+
}
247+
248+
get _busyIndicatorText() {
249+
return this.i18nBundle.getText(BARCODE_SCANNER_DIALOG_LOADING_TXT);
250+
}
251+
252+
static get dependencies() {
253+
return [
254+
Dialog,
255+
BusyIndicator,
256+
Button,
257+
];
258+
}
259+
}
260+
261+
BarcodeScannerDialog.define();
262+
263+
export default BarcodeScannerDialog;

packages/fiori/src/i18n/messagebundle.properties

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
#This is the resource bundle for the UI5 Web Components
22
#__ldi.translation.uuid=95d47730-48a4-4d6d-92f6-61f8c9d8f274
33

4+
#XBUT: Button text for cancel button in the BarcodeScannerDialog
5+
BARCODE_SCANNER_DIALOG_CANCEL_BUTTON_TXT=Cancel
6+
7+
#XBUT: Text for the busy indicator in the BarcodeScannerDialog
8+
BARCODE_SCANNER_DIALOG_LOADING_TXT=Loading
9+
410
#XTXT: Text for the FlexibleColumnLayout start column
511
FCL_START_COLUMN_TXT=First column
612

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* video */
2+
.ui5-barcode-scanner-dialog-video-wrapper,
3+
.ui5-barcode-scanner-dialog-video {
4+
height:100%;
5+
width: 100%;
6+
}
7+
8+
.ui5-barcode-scanner-dialog-video {
9+
object-fit: cover;
10+
}
11+
12+
/* footer */
13+
.ui5-barcode-scanner-dialog-footer {
14+
display: flex;
15+
justify-content: flex-end;
16+
width: 100%;
17+
padding: 0 0.5rem 0 0;
18+
}
19+
20+
[dir="rtl"] .ui5-barcode-scanner-dialog-footer {
21+
padding: 0 0 0 0.5rem;
22+
}
23+
24+
/* busy indicator */
25+
.ui5-barcode-scanner-dialog-busy {
26+
position: absolute;
27+
top: 50%;
28+
left: 50%;
29+
transform: translate(-50%, -50%);
30+
z-index: 1;
31+
}
32+
33+
.ui5-barcode-scanner-dialog-busy:not([active]) {
34+
display: none;
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6+
<meta charset="utf-8">
7+
8+
<title>Barcode Scanner</title>
9+
10+
<script data-ui5-config type="application/json">
11+
{
12+
"rtl": false
13+
}
14+
</script>
15+
16+
<script>delete Document.prototype.adoptedStyleSheets</script>
17+
<script src="../../webcomponentsjs/webcomponents-loader.js"></script>
18+
<script src="../../resources/bundle.esm.js" type="module"></script>
19+
<script nomodule src="../../resources/bundle.es5.js"></script>
20+
21+
</head>
22+
23+
<body style="background-color: var(--sapBackgroundColor);">
24+
25+
<ui5-barcode-scanner-dialog id="dlgScan"></ui5-barcode-scanner-dialog>
26+
27+
<ui5-button id="btnScan" icon="camera" title="Start Camera">Scan</ui5-button>
28+
<div>
29+
<ui5-label id="scanResult"></ui5-label>
30+
<ui5-label id="scanError"></ui5-label>
31+
</div>
32+
33+
<script>
34+
btnScan.addEventListener("click", function(event) {
35+
dlgScan.show();
36+
});
37+
38+
dlgScan.addEventListener("ui5-scan-success", function(event) {
39+
scanResult.innerHTML = event.detail.text;
40+
dlgScan.close();
41+
});
42+
43+
dlgScan.addEventListener("ui5-scan-error", function(event) {
44+
scanError.innerHTML = event.detail.message;
45+
dlgScan.close();
46+
});
47+
</script>
48+
</body>
49+
</html>

0 commit comments

Comments
 (0)