You might be asked to develop an application in Oracle APEX that can take pictures using a device's camera and save them in the database. This section is intended for the same purpose. You will apply HTML5 features in combination with standard APEX functionality to take pictures from within an Oracle APEX application using a camera, and simultaneously put them in a database table. The exercise will use a table named DEMO_PRODUCT_INFO to demonstrate how to capture an image from your device's camera and store it in this table. If you are new to Oracle APEX, then watch this playlist.
Here are the steps for this exercise:
1. Using the Shared Components | Static Application Files option, upload spin.min.js file from the CameraIntegration folder. The file contains instructions to display a wait spinner while AJAX requests are executing.
2. Open Products page (Page 3) of the sales application in Page Designer. For further details of this application, watch this video.
3. Add a
button to the page using the following properties. The button will call Page 127
(to be created next). The target page will be used to activate device’s camera
to take picture.
Property |
Value |
Button Name |
CAMERA |
Label |
Snap
Picture |
Region |
Breadcrumb |
Button Position |
Create |
Button Template |
Text
with Icon |
Hot |
Yes |
Icon |
fa-camera |
Action |
Redirect
to Page in this application |
Target |
Type=Page
in this application Page=127 Clear
Cache=127 |
4.
Create
a blank page (127), and set the following attributes for this page:
Property |
Value |
Name |
Camera
Integration |
Title |
Take
Snapshot |
Page Mode |
Modal
Dialog |
Width |
400 |
Height |
700 |
File URLs (under
JavaScript) |
#APP_IMAGES#spin.min.js |
Function and Global Variable
Declaration (under JavaScript) |
var video = document.getElementById('myVideo'); var streamVideo; var canvas = document.getElementById('myCanvas'); var ctx = canvas.getContext('2d'); var spinner; var spinForm = document.getElementById('wwvFlowForm'); var spinAttr = { position: 'absolute', className: 'spinner', lines: 10, width: 10, length: 30, top: '50%', left: '50%', radius: 40, opacity: 0.10, speed: 1, color: '#ffffff', rotate: 0 } |
5. Expand
the Pre-Rendering node, and add the following After Header process. It’s
an APEX Collection, which will be used to temporarily store
the pictures you take. Each Collection in APEX can include one BLOB column (BLOB001). Here, you will use this
column to store the binary representation of each picture. The code below
creates an empty Collection named SNAPSHOT every time you access Page 127.
Property |
Value |
Name |
Create
Collection |
Type |
PL/SQL Code |
Location |
Local
Database |
PL/SQL Code |
declare product_image constant apex_collections.collection_name%type := 'SNAPSHOT'; begin if not apex_collection.collection_exists(product_image) then apex_collection.create_collection(p_collection_name => product_image); ELSE apex_collection.delete_collection(p_collection_name => 'SNAPSHOT'); apex_collection.create_collection(p_collection_name => product_image); end if; end; |
Point |
After
Header |
6. Create
a Static Content region using the following attributes. The HTML5 video element enables camera streaming. The autoplay attribute used in this tag specifies
that the camera will start playing as soon as it is ready. The canvas element is normally used to draw
graphics, on the fly, via JavaScript.
Property |
Value |
Title |
Camera |
Type |
Static
Content |
Text |
<div
style="text-align:center;"> <video id="myVideo" width="250"
height="300" autoplay></video> <canvas id="myCanvas"
width="250" height="300"
style="display:none;"></canvas> </div> |
Position |
Content
Body |
Template |
Standard |
7. Add
two text field items as follows. These fields will receive product name and file
name for the captured image.
Property |
Value Text Field 1 |
Value Text Field 2 |
Name |
P127_PRODUCT_NAME |
P127_FILENAME |
Type |
Text
Field |
Text
Field |
Label |
Product
Name |
File
Name |
Region |
Camera |
Camera |
Template |
Required
- Floating |
Required
- Floating |
Value Required |
Yes |
Yes |
8. Create
a Buttons region using the following attributes. The region will hold two
buttons to be created in the next step.
Property |
Value |
Title |
Buttons |
Type |
Static
Content |
Position |
Dialog
Footer |
Template |
Buttons
Container |
9. Add
the following two buttons to the Buttons region. The TAKESNAP button will be
used to take the snapshot via a Dynamic Action (Take Snap).
Property |
Value Button 1 |
Value Button 2 |
Button Name |
CANCEL |
TAKESNAP |
Label |
Cancel |
Take
Snap |
Region |
Buttons |
Buttons |
Button Position |
Below
Region |
Below
Region |
Button Template |
Text |
Text
with Icon |
Hot |
No |
Yes |
Icon |
- |
fa-camera |
Action |
Defined
by Dynamic Action |
Defined
by Dynamic Action |
Execute Validations |
Yes |
Yes |
10. Create
the Take Snap dynamic action as
follows, which executes a JavaScript code to take pictures. The first line in this
code displays a wait spinner through the spin.min.js file you uploaded in step 1. The apex.server.process function calls a PL/SQL on-demand (Ajax callback)
process named GRAB_PICTURE – see Step 13.
Property |
Value |
Name |
Take
Snap |
Event |
Click |
Selection Type |
Button |
Item(s) |
TAKESNAP |
Action |
Execute
JavaScript Code |
Code |
mySpinner = new Spinner(spinAttr).spin(spinForm); ctx.drawImage(video, 0, 0, 250, 300); video.style.display = 'none'; canvas.style.display = 'inline-block'; streamVideo.getTracks()[0].stop(); apex.server.process ( 'GRAB_PICTURE', {p_clob_01: canvas.toDataURL().match(/,(.*)$/)[1]}, {success: function(data) { if (data.result == 'success') { apex.submit('TAKESNAP'); } } } ); |
11. Add another dynamic action to start the camera through JavaScript when the page loads. The navigator.mediaDevices.getUserMedia is a JavaScript method used to access a user's camera and microphone on a web page. It prompts the user for permission to use the camera and microphone, and if granted, returns a MediaStream object that can be used to access the camera and microphone data. The code below contains instructions for both the front and rear device cameras. However, for this exercise, we are using the back camera, and have disabled the code for the front camera (displayed in green color). Both code snippets use the getUserMedia() method to request access to the video stream from the device's camera. The option { video: { facingMode: "environment" } } is used to specify that we want to use the back camera. Once the user grants permission, the video stream is passed to the srcObject property of a <video> element, and the play() method is called to start the video.
Property |
Value |
Name |
Start
Camera |
Event |
Page
Load |
Action |
Execute
JavaScript Code |
Code |
// Use Rear Camera // Check if the browser supports the mediaDevices API if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) // get access to the device's camera { navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) { // Display the camera stream streamVideo = stream; video.srcObject = stream; video.play(); }).catch(error => { // handle error console.log(error); }); } else { // the browser does not support the mediaDevices API console.log("Your browser does not support the MediaDevices API."); } /* // Use Front Camera // Check if the browser supports the mediaDevices API if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) // get access to the device's camera { navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) { // Display the camera stream streamVideo = stream; video.srcObject = stream; video.play(); }).catch(error => { // handle error console.log(error); }); } else { // the browser does not support the mediaDevices API console.log("Your browser does not support the MediaDevices API."); } */ |
12. Add
one more dynamic action to
close the modal page when the Cancel button is clicked.
Property |
Value |
Name |
Cancel
Dialog |
Event |
Click |
Selection Type |
Button |
Button |
CANCEL |
Action |
Cancel
Dialog |
Event |
Cancel
Dialog |
13. Click the Processing tab, and add the following AJAX Call process. This process was referenced in the Take Snap dynamic action earlier. The PL/SQL code of this process uses the APEX_WEB_SERVICE API, which enables you to integrate other systems with Application Express. The APEX_WEB_SERVICE.CLOBBASE64TBLOB is a function provided by the APEX_WEB_SERVICE package in the Oracle Application Express (APEX) framework. It is used to convert a Base64-encoded CLOB to a BLOB. This function takes in one parameter as an input, which is the base64-encoded CLOB and returns the BLOB. This function is useful when working with web services that return binary data, such as images or documents, encoded as Base64 strings. After converting a CLOB datatype into a BLOB, the BLOB is inserted in the SNAPSHOT APEX collection. A CLOB (Character Large Object) is a data type used to store large amounts of character data in a database, while a BLOB (Binary Large Object) is a data type used to store large amounts of binary data. Converting a CLOB to a BLOB typically involves converting the character data to binary data. This can be done for a variety of reasons, such as to improve data storage efficiency, to improve data transfer performance, or to make the data more compatible with other systems or software.
Property |
Value |
Name |
GRAB_PICTURE |
Type |
PL/SQL Code |
Location |
Local Database |
PL/SQL Code |
declare
V_picture_clob clob;
V_picture_blob blob; begin
V_picture_clob := apex_application.g_clob_01;
V_picture_blob := apex_web_service.clobbase642blob( p_clob => V_picture_clob );
apex_collection.add_member(
p_collection_name => 'SNAPSHOT', p_blob001
=> V_picture_blob );
apex_json.open_object;
apex_json.write( p_name
=> 'result', p_value
=> 'success' );
apex_json.close_object; exception when others
then
apex_json.open_object;
apex_json.write( p_name
=> 'result', p_value
=> 'fail' );
apex_json.close_object; end; |
Point |
Ajax
Callback |
14. Add
the following process (under Processing)
to get the captured image from the SNAPSHOT collection and put it into the
DEMO_PRODUCT_INFO table.
Property |
Value |
Name |
Save
Picture |
Type |
PL/SQL Code |
Location |
Local Database |
PL/SQL Code |
BEGIN INSERT
INTO DEMO_PRODUCT_INFO (product_name,
filename, mimetype, image_last_update, Product_image
) VALUES (:P127_PRODUCT_NAME,
:P127_FILENAME||'.jpg', 'image/jpeg', sysdate,
(SELECT blob001 FROM apex_collections WHERE collection_name
= 'SNAPSHOT') ); -- Delete
APEX collection IF apex_collection.collection_exists(p_collection_name
=> 'SNAPSHOT') THEN
apex_collection.delete_collection(p_collection_name => 'SNAPSHOT'); END IF; END; |
Sequence |
10 |
Point |
Processing |
When Button Pressed |
TAKESNAP |
15. Add
the following process to complete the image-capturing process. This process,
which is to be placed after the Save Picture process, will close the modal page
after taking the snapshot.
Property |
Value |
Name |
Close
Dialog |
Type |
Close
Dialog |
Sequence |
20 |
Point |
Processing |
16. One
last step that you need to perform is to automatically refresh the reports page
to display the pictures you take. Open the Products page (Page 3) in page
designer to add the following dynamic action.
Property |
Value |
Name |
Refresh
Report |
Event |
Dialog
Closed |
Selection Type |
Button |
Button |
CAMERA |
Action |
Refresh |
Selection Type |
Region |
Region |
Products |
Run the application on your laptop with a camera, or on your Smartphone. Click the Products menu item. On the Products report page (Page 3), click the Snap Picture button that was added to the page in Step 3. Your browser might ask you to allow access to your device's camera. After granting access, the video should start playing on the modal page. Enter something in the Product Name and File Name text fields. Do not append an extension to the file name, because a .jpg extension will be added to it by default – see the Save Picture process. Take a picture by clicking the Take Snap button. The picture will be taken and you will be landed back on Page 3, where you will see the picture added to the interactive report.