Native Smartface & WebView Interaction

Using a Hybrid Approach in a Fully Native Environment

Smartface is a fully native development environment with 100% native API access, however, it has a JavaScript based architecture and Smartface applications developed with JavaScript exhibit similarities with Web apps developed via JavaScript. The main difference is that Smartface itself has its own implementation of the UI objects (sf-core) without any use of the DOM which is the basis of the visual portions of the web pages.

Without the DOM support in Smartface due to its native architecture, you cannot use other Web frameworks and libraries such as jQuery or Angular or reactJS. In those cases, Smartface WebView comes in handy, enabling the display of the web content (like a browser) within the App as a component.

Smartface JavaScript engine has its own scope and each WebView has it own scope. They are not interacting to each other directly but there are ways to establish a connection between each other.

In the rest of this guide, WebView component will be used intensively; assuming the reader is knowledgeable about WebView Guide and WebView API Reference.

WebView Code Injection

Smartface WebView instance has a evaluateJS method to run a code block within the displayed Web Page content.

This method takes the script as a string. You cannot pass objects between native app and the WebView directly, as the Smartface app and the Web Page have their own scopes. If you want to pass objects, you can serialize them as JSON and deserialize them on the other side.

Add Smartface Logo to Web Page
Add Smartface Logo to Web Page
var img = document.createElement("img");
img.src = "http://account.smartface.io/Images/logo.png";
document.body.insertBefore(img, document.body.firstChild);

Given code above adds the Smartface logo to the web page, which should be running inside the web page scope.

To run the code in the web page contained in the WebView, following things should be done in order:

  1. Convert your script-to-run to string a. The injected code needs to run on document load!

  2. Have your WebView instance set up

  3. After page is loaded, evaluate the script string

1 Script as String
1 Script as String
var script = `
function insertLogo() {
var img = document.createElement("img");
img.src = "http://account.smartface.io/Images/logo.png";
document.body.insertBefore(img, document.body.firstChild);
}
if (document.readyState === "complete")
insertLogo();
else
document.onload = insertLogo();
`;

The sample code given above is for modifying the document.onLoad event. If you are using jQuery you can use document.ready().

2 & 3 WebView Setup and run Script onLoad
2 & 3 WebView Setup and run Script onLoad
const WebView = require("sf-core/ui/webview");
const Flex = require("sf-core/ui/flexlayout")
var url = "http://example.com/";
var myWebView = new WebView({
left: 0,
top: 0,
right: 0,
bottom: 0,
positionType: FlexLayout.PositionType.ABSOLUTE,
onShow: function(event) {
console.log(event.url);
if (url === event.url) { //make sure script is running only on specific url(s)
console.log("about to eval");
myWebView.evaluateJS(script);
}
}
});
page.layout.addChild(myWebView);
myWebView.loadURL(url);
page.js
page.js
const extend = require('js-base/core/extend');
const Page = require('sf-core/ui/page');
const WebView = require('sf-core/ui/webview');
const FlexLayout = require('sf-core/ui/flexlayout');
var script = `
function insertLogo() {
var img = document.createElement("img");
img.src = "http://account.smartface.io/Images/logo.png";
document.body.insertBefore(img, document.body.firstChild);
}
if (document.readyState === "complete")
insertLogo();
else
document.onload = insertLogo();
`;
const MyPage = extend(Page)(
function(_super) {
var page = this;
_super(this);
var url = "http://example.com/";
var myWebView = new WebView({
left: 0,
top: 0,
right: 0,
bottom: 0,
positionType: FlexLayout.PositionType.ABSOLUTE,
onShow: function(event) {
console.log(event.url);
if (url === event.url) { //make sure script is running only on specific url(s)
console.log("about to eval");
myWebView.evaluateJS(script);
}
}
});
page.layout.addChild(myWebView);
this.onShow = function() {
myWebView.loadURL(url);
};
});
module && (module.exports = MyPage);

Using WebView Bridge (util)

WebView Bridge (a util library) allows bi-directional communication with Web Page. The usages mentioned above, they all are invoked from Native side to Web Page: A JS code is executed and the synchronous response is parsed on the app. For a press of a button, a timer event, a web socket event on the Web Page can cause an action on the native side. This util library allows event and event data flow to the native side. In order to trigger an action on the native side, using this WebView Bridge, following code should be executed:

WebPage JavaScript
WebPage JavaScript
window.boubleEvent("eventName", eventDataObject); //using window variable is recommended

It is possible to add this call to the Web Page from the source of the Web Page (if the developer can access its source code & change it), or it is possible to inject that code from Native using evaluateJS

Web Page HTML
Web Page HTML
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<button id="btn">Press me</button>
</body>
</html>

If the developer can change the code, the HTMLwill be like this:

Modified Web Page HTML
Modified Web Page HTML
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<button id="btn">Press me</button>
<script type="text/javascript">
var counter = 0;
var btn = document.getElementById("btn");
btn.addEventListener("click", function() {
window.boubleEvent("eventName", {
pressCount: counter
});
counter++;
});
</script>
</body>
</html>

Another approach is to inject that code from native side. There is no need to use them both; use only one of them.

Event Injection
Event Injection
const wvb = page.wvb = new WebViewBridge({
webView: page.webView,
source: "http://www.google.com"
});
page.wvb = wvb;
wvb.on("myEvent", () => {
alert("myEvent", "Event fired");
});
wvb.ready().then(()=>{
wvb.evaluateJS(script);
});
const script = `
setTimeout(()=> {
window.boubleEvent("myEvent");
}, 1000);
`;

Example Use Cases

  • Display charts or graphics (one way interactions work best for using WebViews in a native app)

  • Logins provided via web (e.g. Oauth Implicit flow)

  • Remove or hide unwanted elements, such as logos from the web page

  • Share authentication

  • Display other components that are on the web that require some data from the app

Example 1: Hide Elements in a Web Page

In this example, a web page inside WebView is shown and 2 native buttons are added to bottom of the native Smartface page. Those buttons are performing show/hide operations within the web page.

Web Page HTML
Web Page HTML
<!DOCTYPE html>
<html>
<head>
<title>Smartface - WebView Hide & Show Example </title>
<style type="text/css">
#item {
background-color: RoyalBlue;
color: white;
width: calc(100% - 40px);
height: calc(100% - 40px);
margin: 20px;
display: flex;
align-items: center;
font-size: 72pt;
}
#item div {
width: 100%;
text-align: center;
display: block;
}
html,
body {
height: 100%;
margin: 0;
background-color: DarkSeaGreen;
}
</style>
</head>
<body>
<div id="item">
<div>Item</div>
</div>
</body>
</html>

Link to the web page is also available: https://az793023.vo.msecnd.net/examples/sf-core/webview/hide-show.html Abstract Smartface code is given below:

Smartface Code
Smartface Code
var btnHide = new Button({
onPress: function() {
page.webview1.evaluateJS('document.getElementById("item").style.display="none";');
}
});
var btnShow = new Button({
onPress: function() {
page.webview1.evaluateJS('document.getElementById("item").style.display="flex";');
}
});
page.onShow = function(e) {
page.webview1.loadURL("https://az793023.vo.msecnd.net/examples/sf-core/webview/hide-show.html");
};
Page Hide Show
Page Hide Show
const extend = require('js-base/core/extend');
const Page = require('sf-core/ui/page');
const WebView = require('sf-core/ui/webview');
const FlexLayout = require('sf-core/ui/flexlayout');
const Color = require('sf-core/ui/color');
const Button = require('sf-core/ui/button');
const TextAlignment = require('sf-core/ui/textalignment');
const Font = require('sf-core/ui/font');
const PageHideShow = extend(Page)(
function(_super) {
var page = this;
_super(this, {
onLoad: onLoad.bind(this),
orientation: Page.Orientation.PORTRAIT
});
var webview1 = new WebView({
positionType: FlexLayout.PositionType.RELATIVE,
bottom: 0,
flexGrow: 1,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true
});
this.layout.addChild(webview1);
this.webview1 = webview1;
var flexlayout1 = new FlexLayout({
height: 70,
alignContent: FlexLayout.AlignContent.STRETCH,
alignItems: FlexLayout.AlignItems.STRETCH,
justifyContent: FlexLayout.JustifyContent.FLEX_START,
flexWrap: FlexLayout.FlexWrap.NOWRAP,
flexDirection: FlexLayout.FlexDirection.ROW,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true
});
this.layout.addChild(flexlayout1);
var btnHide = new Button({
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
flexGrow: 1,
backgroundColor: Color.create(255, 144, 19, 254),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create("#FFFFFF"),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
text: "Hide",
onPress: function() {
page.webview1.evaluateJS('document.getElementById("item").style.display="none";');
}
});
btnHide.font = Font.create("Arial", 16, Font.NORMAL);
flexlayout1.addChild(btnHide);
var btnShow = new Button({
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
flexGrow: 1,
backgroundColor: Color.create(255, 74, 144, 226),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create("#FFFFFF"),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
text: "Show",
onPress: function() {
page.webview1.evaluateJS('document.getElementById("item").style.display="flex";');
}
});
btnShow.font = Font.create("Arial", 16, Font.NORMAL);
flexlayout1.addChild(btnShow);
//assign the children to page
this.children = Object.assign({}, {
webview1: webview1,
flexlayout1: flexlayout1
});
//assign the children of flexlayout1
flexlayout1.children = Object.assign({}, {
btnHide: btnHide,
btnShow: btnShow
});
page.onShow = function(e) {
page.webview1.loadURL("https://az793023.vo.msecnd.net/examples/sf-core/webview/hide-show.html");
};
});
function onLoad() {
this.headerBar.title = "Hide Show";
this.headerBar.titleColor = Color.create(255, 0, 0, 0);
this.headerBar.visible = true;
Application.statusBar.visible = true;
this.layout.flexDirection = FlexLayout.FlexDirection.COLUMN;
this.layout.alignItems = FlexLayout.AlignItems.STRETCH;
this.layout.direction = FlexLayout.Direction.INHERIT;
this.layout.flexWrap = FlexLayout.FlexWrap.NOWRAP;
this.layout.justifyContent = FlexLayout.JustifyContent.SPACE_AROUND;
this.layout.backgroundColor = Color.create("#EEEEEE");
}
module && (module.exports = PageHideShow);

Example 2: Display a chart

In this example, an HTML Chart is shown within the WebView and its data is being modified (set) by the native Smartface code.

Chart example has been taken from Chart.js and slightly modified for demo purposes. Used sample page is on https://az793023.vo.msecnd.net/examples/sf-core/webview/chart.html

Chart Page
Chart Page
const extend = require('js-base/core/extend');
const Page = require('sf-core/ui/page');
const WebView = require('sf-core/ui/webview');
const FlexLayout = require('sf-core/ui/flexlayout');
const Color = require('sf-core/ui/color');
const Button = require('sf-core/ui/button');
const TextAlignment = require('sf-core/ui/textalignment');
const Font = require('sf-core/ui/font');
const pgChart = extend(Page)(
function(_super) {
var page = this;
_super(this, {
onLoad: onLoad.bind(this),
orientation: Page.Orientation.PORTRAIT
});
var webview1 = new WebView({
positionType: FlexLayout.PositionType.RELATIVE,
bottom: 0,
flexGrow: 1,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true
});
this.layout.addChild(webview1);
this.webview1 = webview1;
var flexlayout1 = new FlexLayout({
height: 140,
alignContent: FlexLayout.AlignContent.STRETCH,
alignItems: FlexLayout.AlignItems.STRETCH,
justifyContent: FlexLayout.JustifyContent.FLEX_START,
flexWrap: FlexLayout.FlexWrap.WRAP,
flexDirection: FlexLayout.FlexDirection.ROW,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true
});
this.layout.addChild(flexlayout1);
var btnRandomizedata = new Button({
width: 180,
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create(255, 112, 106, 53),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create("#FFFFFF"),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
text: "Randomize data",
onPress: function() {
var script = `
config.data.datasets.forEach(function(dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myLine.update();`;
page.webview1.evaluateJS(script);
}
});
btnRandomizedata.font = Font.create("Arial", 16, Font.NORMAL);
flexlayout1.addChild(btnRandomizedata);
var btnAddDataset = new Button({
width: 120,
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create(255, 47, 109, 50),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create("#FFFFFF"),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
text: "Add Dataset",
onPress: function() {
var script = `
var colorName = colorNames[config.data.datasets.length % colorNames.length];
var newColor = window.chartColors[colorName];
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
backgroundColor: newColor,
borderColor: newColor,
data: [],
fill: false
};
for (var index = 0; index < config.data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}
config.data.datasets.push(newDataset);
window.myLine.update();`;
page.webview1.evaluateJS(script);
}
});
btnAddDataset.font = Font.create("Arial", 16, Font.NORMAL);
flexlayout1.addChild(btnAddDataset);
var btnRemoveDataset = new Button({
width: 150,
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create(255, 179, 23, 23),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create("#FFFFFF"),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
text: "Remove Dataset",
onPress: function() {
var script = `
config.data.datasets.splice(0, 1);
window.myLine.update();`;
page.webview1.evaluateJS(script);
}
});
btnRemoveDataset.font = Font.create("Arial", 16, Font.NORMAL);
flexlayout1.addChild(btnRemoveDataset);
var btnAddData = new Button({
width: 100,
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create(255, 107, 179, 23),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create("#FFFFFF"),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
text: "Add Data",
onPress: function() {
var script = `
if (config.data.datasets.length > 0) {
var month = MONTHS[config.data.labels.length % MONTHS.length];
config.data.labels.push(month);
config.data.datasets.forEach(function(dataset) {
dataset.data.push(randomScalingFactor());
});
window.myLine.update();
}`;
page.webview1.evaluateJS(script);
}
});
btnAddData.font = Font.create("Arial", 16, Font.NORMAL);
flexlayout1.addChild(btnAddData);
var btnRemoveData = new Button({
width: 120,
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create(255, 179, 23, 179),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create("#FFFFFF"),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
text: "Remove Data",
onPress: function() {
var script = `
config.data.labels.splice(-1, 1); // remove the label first
config.data.datasets.forEach(function(dataset, datasetIndex) {
dataset.data.pop();
});
window.myLine.update();`;
page.webview1.evaluateJS(script);
}
});
btnRemoveData.font = Font.create("Arial", 16, Font.NORMAL);
flexlayout1.addChild(btnRemoveData);
//assign the children to page
this.children = Object.assign({}, {
webview1: webview1,
flexlayout1: flexlayout1
});
//assign the children of flexlayout1
flexlayout1.children = Object.assign({}, {
btnRandomizedata: btnRandomizedata,
btnAddDataset: btnAddDataset,
btnRemoveDataset: btnRemoveDataset,
btnAddData: btnAddData,
btnRemoveData: btnRemoveData
});
page.onShow = function(e) {
page.webview1.loadURL("https://az793023.vo.msecnd.net/examples/sf-core/webview/chart.html");
};
});
function onLoad() {
this.headerBar.title = "Chart";
this.headerBar.titleColor = Color.create(255, 0, 0, 0);
this.headerBar.visible = true;
Application.statusBar.visible = true;
this.layout.flexDirection = FlexLayout.FlexDirection.COLUMN;
this.layout.alignItems = FlexLayout.AlignItems.STRETCH;
this.layout.direction = FlexLayout.Direction.INHERIT;
this.layout.flexWrap = FlexLayout.FlexWrap.NOWRAP;
this.layout.justifyContent = FlexLayout.JustifyContent.SPACE_AROUND;
this.layout.backgroundColor = Color.create("#EEEEEE");
}
module && (module.exports = pgChart);

Example 3: Pass UserName and Password to WebView

In this example, there are two Smartface pages:

  1. Smartface Login Page

  2. WebView to pass the credentials

In order to run the example, you need to have a Smartface account registered via email & password. On the login page, entered credentials are passed to the WebView, filling the form and submitting to thelogin.

Page 1 - Login
Page 1 - Login
const extend = require('js-base/core/extend');
const Page = require('sf-core/ui/page');
const FlexLayout = require('sf-core/ui/flexlayout');
const Color = require('sf-core/ui/color');
const ImageView = require('sf-core/ui/imageview');
const Image = require('sf-core/ui/image');
const ImageFillType = require('sf-core/ui/imagefilltype');
const Button = require('sf-core/ui/button');
const TextAlignment = require('sf-core/ui/textalignment');
const Font = require('sf-core/ui/font');
const TextBox = require('sf-core/ui/textbox');
const StatusBarStyle = require('sf-core/ui/statusbarstyle');
const KeyboardType = require('sf-core/ui/keyboardtype');
const ActionKeyType = require('sf-core/ui/actionkeytype');
const Router = require("sf-core/ui/router");
const PgLogin = extend(Page)(
//constructor
function(_super) {
// initalizes super class for this page scope
_super(this, {
onLoad: onLoad.bind(this),
orientation: Page.Orientation.PORTRAIT
});
var page = this;
var flLeft = new FlexLayout({
alignContent: FlexLayout.AlignContent.STRETCH,
alignItems: FlexLayout.AlignItems.STRETCH,
justifyContent: FlexLayout.JustifyContent.FLEX_START,
flexWrap: FlexLayout.FlexWrap.NOWRAP,
flexDirection: FlexLayout.FlexDirection.COLUMN,
positionType: FlexLayout.PositionType.RELATIVE,
flexGrow: 1,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true
});
this.layout.addChild(flLeft);
var flMid = new FlexLayout({
alignContent: FlexLayout.AlignContent.STRETCH,
alignItems: FlexLayout.AlignItems.STRETCH,
justifyContent: FlexLayout.JustifyContent.SPACE_AROUND,
flexWrap: FlexLayout.FlexWrap.NOWRAP,
flexDirection: FlexLayout.FlexDirection.COLUMN,
positionType: FlexLayout.PositionType.RELATIVE,
flexGrow: 5,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true
});
this.layout.addChild(flMid);
var flRight = new FlexLayout({
alignContent: FlexLayout.AlignContent.STRETCH,
alignItems: FlexLayout.AlignItems.STRETCH,
justifyContent: FlexLayout.JustifyContent.FLEX_START,
flexWrap: FlexLayout.FlexWrap.NOWRAP,
flexDirection: FlexLayout.FlexDirection.COLUMN,
positionType: FlexLayout.PositionType.RELATIVE,
flexGrow: 1,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true
});
this.layout.addChild(flRight);
var imgLogo = new ImageView({
height: 100,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true,
image: Image.createFromFile("images://smartface.png"),
imageFillType: ImageFillType.ASPECTFIT
});
flMid.addChild(imgLogo);
var flInputs = new FlexLayout({
height: 155,
alignContent: FlexLayout.AlignContent.STRETCH,
alignItems: FlexLayout.AlignItems.STRETCH,
justifyContent: FlexLayout.JustifyContent.SPACE_BETWEEN,
flexWrap: FlexLayout.FlexWrap.NOWRAP,
flexDirection: FlexLayout.FlexDirection.COLUMN,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create("#FFFFFF"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
visible: true
});
flMid.addChild(flInputs);
var btnLogin = new Button({
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create("#00A1F1"),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create("#FFFFFF"),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
text: "Login to Smartface",
onPress: function() {
doLogin();
}
});
this.btnLogin = btnLogin;
btnLogin.font = Font.create("Arial", 16, Font.NORMAL);
flMid.addChild(btnLogin);
var tbEmail = new TextBox({
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create(255, 238, 238, 238),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create(255, 0, 0, 0),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
hint: "eMail",
keyboardType: KeyboardType.EMAILADDRESS,
onActionButtonPress: function() {
page.tbPassword.requestFocus();
},
actionKeyType: ActionKeyType.NEXT
});
this.tbEmail = tbEmail;
tbEmail.font = Font.create("Arial", 16, Font.NORMAL);
flInputs.addChild(tbEmail);
var tbPassword = new TextBox({
height: 70,
positionType: FlexLayout.PositionType.RELATIVE,
backgroundColor: Color.create(255, 238, 238, 238),
alpha: 1,
borderColor: Color.create(255, 0, 0, 0),
borderWidth: 0,
textColor: Color.create(255, 0, 0, 0),
textAlignment: TextAlignment.MIDCENTER,
visible: true,
hint: "Password",
isPassword: true,
onActionButtonPress: function() {
doLogin();
},
actionKeyType: ActionKeyType.GO
});
this.tbPassword = tbPassword;
tbPassword.font = Font.create("Arial", 16, Font.NORMAL);
flInputs.addChild(tbPassword);
//assign the children to page
this.children = Object.assign({}, {
flLeft: flLeft,
flMid: flMid,
flRight: flRight
});
//assign the children of flMid
flMid.children = Object.assign({}, {
imgLogo: imgLogo,
flInputs: flInputs,
btnLogin: btnLogin
});
//assign the children of flInputs
flInputs.children = Object.assign({}, {
tbEmail: tbEmail,
tbPassword: tbPassword
});
function doLogin() {
if(page.tbEmail.text.length > 0 && page.tbPassword.text.length > 0) {
Router.go("pgLogin2", {
email: page.tbEmail.text,
password: page.tbPassword.text
});
}
}
});
function onLoad() {
this.headerBar.title = "Login to Smartface";
this.headerBar.titleColor = Color.create(255, 255, 255, 255);
this.headerBar.backgroundColor = Color.create(255, 0, 161, 241);
this.headerBar.visible = true;
Application.statusBar.visible = true;
Application.statusBar.android && (Application.statusBar.android.color = Color.create(255, 0, 161, 241));
Application.statusBar.ios && (Application.statusBar.ios.style = StatusBarStyle.LIGHTCONTENT);
this.layout.alignContent = FlexLayout.AlignContent.STRETCH;
this.layout.alignItems = FlexLayout.AlignItems.STRETCH;
this.layout.direction = FlexLayout.Direction.INHERIT;
this.layout.flexDirection = FlexLayout.FlexDirection.ROW;
this.layout.flexWrap = FlexLayout.FlexWrap.NOWRAP;
this.layout.justifyContent = FlexLayout.JustifyContent.FLEX_START;
this.layout.backgroundColor = Color.create("#FFFFFF");
}
module && (module.exports = PgLogin);
Page 2- WebView
Page 2- WebView
const extend = require('js-base/core/extend');
const Page = require('sf-core/ui/page');
const WebView = require('sf-core/ui/webview');
const FlexLayout = require('sf-core/ui/flexlayout');
var script = `
function doLogin() {
var emailInput = document.querySelector("#siwe > form > div > div.user-login-info > input:nth-child(1)");
var passwordInput = document.querySelector("#siwe > form > div > div.user-login-info > input:nth-child(2)");
var btnLogin = document.querySelector("#siwe > form > div > button");
emailInput.value = "$email";
passwordInput.value = "$password";
btnLogin.click();
}
if (document.readyState === "complete")
doLogin();
else
document.onload = doLogin();
`;
const pgLogin2 = extend(Page)(
function(_super) {
var page = this;
_super(this);
var email, password;
var url = "http://cloud.smartface.io/";
var myWebView = new WebView({
left: 0,
top: 0,
right: 0,
bottom: 0,
positionType: FlexLayout.PositionType.ABSOLUTE,
onShow: webViewURLHandling,
onChangedURL: webViewURLHandling
});
page.layout.addChild(myWebView);
function webViewURLHandling(event) {
if (event.url.startsWith("https://id.smartface.io/identity/login?signin=")) {
var jsScript = script.replace("$email", email).replace("$password", password);
myWebView.evaluateJS(jsScript);
}
}
this.onShow = function(e) {
email = e.email;
password = e.password;
myWebView.loadURL(url);
};
});
module && (module.exports = pgLogin2);

Example 4: Getting button press from WebView

In this example, let's assume the app is displaying some web content, such as an advertisement campaign. The content of the page is displayed, at the end of the document there is a button, such as "I am interested". If user press to the button, the app should capture the press and do something on the native side.

Campaign Button Press
Campaign Button Press
const url = "https://az793023.vo.msecnd.net/examples/webviewbridge/buttonevent.html";
const WebViewBridge = require("sf-extension-utils").WebViewBridge;
const script = `
var btn = document.getElementById("btn");
btn.onclick = function() {
window.boubleEvent("buttonPress");
};
`;
//Within constructor
const wvb = page.wvb = new WebViewBridge({
webView: page.wv, //WebView
source: url
});
wvb.on("buttonPress", (data) => {
console.log("button pressed");
//Do your own logic
});
wvb.ready().then(() => {
wvb.evaluateJS(script);
});

Example 5: WebSocket - Capture Chats

In this example, a third party WebSocket based chat demo page is used and this page is not affiliated with Smartface. In order to experience the demo, please open the WebPage in your browser and run the app.

WebSocket
WebSocket
const url = "https://html5demos.com/web-socket/";
const WebViewBridge = require("sf-extension-utils").WebViewBridge;
const script = `
window.conn.onmessage = function(superOnMessage, message) {
superOnMessage(message);
window.boubleEvent("message", {
text: event.data
});
}.bind(window.conn, window.conn.onmessage.bind(window.conn));
`;
//Within constructor
const wvb = page.wvb = new WebViewBridge({
webView: page.wv, //WebView
source: url
});
wvb.on("buttonPress", (data) => {
console.log(`Message recieved containing: ${data.text}`);
//Do your own logic
});
wvb.ready().then(() => {
wvb.evaluateJS(script);
});

Example 6: Following Redirections & WebView Bridge

In this example, Smartface Cloud web authentication will be executed. WebView bridge is injecting code whenever the page is loaded. Some pages do a javascript based redirection. This can cause a conflict, a side effect with WebView Bridge. In order to prevent conflicts, a delay to the pages might be introduced. Please continue to experience this example after login.

WebView Bridge with Delay
WebView Bridge with Delay
const WebViewBridge = require("sf-extension-utils").WebViewBridge;
const script = `
setTimeout(()=> {
window.boubleEvent("myEvent");
}, 1000);
`;
//Within constructor
const wvb = page.wvb = new WebViewBridge({
webView: page.wv,
source: "http://cloud.smartface.io",
delay: 100
});
wvb.on("myEvent", () => {
alert("myEvent", "Event fired");
});
wvb.ready().then(() => {
wvb.evaluateJS(script);
});

Example 7: WebView Bridge - Loading Dynamic Libraries

WebView bridge also allows loading of other libraries into the WebView. In the documentation of WebView Bridge loadScripts method is used. This method is adding script tags with src value set to the given URL. This URL should be relative to the page. If it is not relative, absolute URL should be used. Loading of the scripts are performed after WebView Bridge is ready. This method also returns a Promise object. This Promise should be used to evaluate scripts which are dependent on the libraries which are loaded with the loadScripts method.

In this example, a cookie string will be injected into the WebView. This cannot be done with JavaScript injection, because the very first initial request should be already authenticated.

Adding Cookie with Native API Access
Adding Cookie with Native API Access
var cookie = "Your full cookie";
var targetUrl = "http://example.com"
if (System.OS === "iOS") {
var MutableRequest = SF.requireClass("NSMutableURLRequest");
var NSURL = SF.requireClass("NSURL");
var request = MutableRequest.requestWithURL(NSURL.URLWithString(targetUrl));
request.addValueForHTTPHeaderField(`${cookie}`, "Cookie");
webview.nativeObject.load(request); //this is equvalent of webview.loadURL
}
else {
const CookieManager = requireClass("android.webkit.CookieManager");
var cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
cookieManager.setCookie(targetUrl, `${cookie}`);
webview.loadURL(targetUrl);
}

evaluateJS Performace

WebView.evaluateJS is used for two things:

  1. To execute a JavaScript on the WebView

  2. Parsing and getting the result of the executed JavaScript statement

Regardless of what both of the steps will be executed. In some cases, the result of the executes JavaScript statement might be too long to parse. This will greatly reduce the performance of the evaluateJS execution. Simply add a single null statement to the end of the JavaScript statement, this will reduce the parsing of the result operation. Util WebView Bridge evaluateJS is designed with that performance trick. Here is the code block from WebView Bridge:

WebView Bridge - evalutateJS
WebView Bridge - evalutateJS
function evaluateJS(javascript, onReceive) {
if (!this.parseResponses) //a flag used for wrapping the code or not
javascript = `(function(){\n${javascript}\n})();null;`; //Wraps the Code in an immedaite function
return this.webView.evaluateJS(javascript, onReceive);
}