MaterialTextBox

API Reference: UI.MaterialTextBox

Material text field with consistent behavior on iOS and Android.

For ease of usage and overcome Platform quirks, an utility of MaterialTextbox is created and added as default to the projects. Link can be found below:

Features

  • Material design guidelines compliance

  • Consistent look and feel on iOS and Android

  • Animated state transitions (normal & error)

  • Customizable font size and colors

Example

There is an example down below showing a login scenario.

TypeScript
JavaScript
TypeScript
import PageSampleDesign from 'generated/pages/pageSample';
import FlexLayout = require('sf-core/ui/flexlayout');
import Application = require('sf-core/application');
import Color = require('sf-core/ui/color');
import Font = require('sf-core/ui/font');
import MaterialTextBox = require('sf-core/ui/materialtextbox');
import Button = require("sf-core/ui/button");
import System = require('sf-core/device/system');
import KeyboardType = require('sf-core/ui/keyboardtype');
import ImageView = require("sf-core/ui/imageview");
//You should create new Page from UI-Editor and extend with it.
export default class Sample extends PageSampleDesign {
flWrapper: FlexLayout;
imgShow: ImageView;
mtbPassword: MaterialTextBox;
btnLogin: Button;
NORMAL_FONT: any = {
font: {
size: 16,
bold: false,
italic: false,
family: "SFProText",
style: "Semibold"
}
};
TB_HEIGHT_ANDROID: number = 85;
isIOS: boolean = System.OS === "iOS";
constructor() {
super();
// Overrides super.onShow method
this.onShow = onShow.bind(this, this.onShow.bind(this));
// Overrides super.onLoad method
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
this.layout.flexDirection = FlexLayout.FlexDirection.ROW;
this.layout.justifyContent = FlexLayout.JustifyContent.CENTER;
this.layout.alignItems = FlexLayout.AlignItems.CENTER;
}
}
/**
* @event onShow
* This event is called when a page appears on the screen (everytime).
* @param {function} superOnShow super onShow function
* @param {Object} parameters passed from Router.go function
*/
function onShow(superOnShow: () => void) {
const { headerBar } = this;
superOnShow();
Application.statusBar.visible = false;
headerBar.visible = false;
}
/**
* @event onLoad
* This event is called once when page is created.
* @param {function} superOnLoad super onLoad function
*/
function onLoad(superOnLoad: () => void) {
superOnLoad();
const { TB_HEIGHT_ANDROID, isIOS, NORMAL_FONT } = this;
this.flWrapper = new FlexLayout();
this.imgShow = new ImageView({
height: 20,
image: "images://smartface.png",
imageFillType: ImageView.FillType.ASPECTFIT
});
this.mtbUsername = new MaterialTextBox({
hint: "Username",
onActionButtonPress: e => this.mtbPassword.requestFocus()
});
this.mtbUsername.onTextChanged = e => {
// Reset error message
this.mtbUsername.errorMessage = "";
this.mtbUsername.enableErrorMessage = false;
};
this.mtbPassword = new MaterialTextBox({
hint: "Password",
onActionButtonPress: e => this.mtbPassword.removeFocus()
});
this.mtbPassword.isPassword = true;
this.mtbPassword.onTextChanged = e => {
// Reset error message
this.mtbPassword.errorMessage = "";
this.mtbPassword.enableErrorMessage = false;
};
this.mtbPassword.android.rippleEnabled = true;
this.mtbPassword.android.rippleColor = Color.RED;
this.mtbPassword.rightLayout = { view: this.imgShow, width: 30 };
this.btnLogin = new Button({
text: "Login",
onPress: (): void => {
// If username or password doesn't exist, show error message
let usernameExists = !!this.mtbUsername.text;
let passwordExists = !!this.mtbPassword.text;
!usernameExists && (this.mtbUsername.errorMessage = "Invalid username");
!passwordExists && (this.mtbPassword.errorMessage = "Invalid password");
}
});
this.mtbUsername.ios.clearButtonEnabled = true;
this.mtbUsername.enableErrorMessage = true;
this.mtbUsername.keyboardType = KeyboardType.android.TEXTSHORTMESSAGE;
this.mtbPassword.ios.clearButtonEnabled = true;
this.mtbPassword.enableErrorMessage = true;
this.layout.addChild(this.flWrapper, "flWrapper", ".sf-flexLayout", {
flexProps: {
alignItems: "CENTER"
},
height: 300
});
let materialtextboxOptions = Object.assign({
width: 200,
font: NORMAL_FONT,
labelsFont: NORMAL_FONT,
borderWidth: 0,
}, isIOS ? {} : { height: TB_HEIGHT_ANDROID });
this.flWrapper.addChild(this.mtbUsername, "mtbUsername", ".sf-textBox", materialtextboxOptions);
this.flWrapper.addChild(this.mtbPassword, "mtbPassword", ".sf-textBox", materialtextboxOptions);
this.flWrapper.addChild(this.btnLogin, "btnLogin", ".sf-button", {
top: 20,
height: 60,
width: 200,
backgroundColor: "#00A1F1"
});
}
JavaScript
const Application = require("sf-core/application");
const ImageView = require("sf-core/ui/imageview");
const Color = require("sf-core/ui/color");
const Page = require('sf-core/ui/page');
const extend = require('js-base/core/extend');
const FlexLayout = require('sf-core/ui/flexlayout');
const MaterialTextBox = require('sf-core/ui/materialtextbox');
const Font = require('sf-core/ui/font');
const Button = require("sf-core/ui/button");
const System = require('sf-core/device/system');
const KeyboardType = require('sf-core/ui/keyboardtype');
module.exports = extend(Page)(
function (_super) {
_super(this, {
onShow: function (params) {
Application.statusBar.visible = false;
this.headerBar.visible = false;
}
});
this.layout.justifyContent = FlexLayout.JustifyContent.CENTER;
const NORMAL_FONT = Font.create(Font.DEFAULT, 22, Font.NORMAL);
const TB_HEIGHT_ANDROID = 85;
var isIOS = System.OS === "iOS";
var flWrapper = new FlexLayout({
alignItems: FlexLayout.AlignItems.CENTER,
height: 300
});
var imgShow = new ImageView({
height: 20,
image: "images://key.png",
imageFillType: ImageView.FillType.ASPECTFIT,
});
var mtbUsername = new MaterialTextBox({
width: 200,
hint: "Username",
onActionButtonPress: e => mtbPassword.requestFocus()
});
mtbUsername.onTextChanged = e => {
// Reset error message
mtbUsername.errorMessage = "";
mtbUsername.enableErrorMessage = false;
};
var mtbPassword = new MaterialTextBox({
width: 200,
hint: "Password",
onActionButtonPress: e => mtbPassword.removeFocus()
});
mtbPassword.isPassword = true;
mtbPassword.onTextChanged = e => {
// Reset error message
mtbPassword.errorMessage = "";
mtbPassword.enableErrorMessage = false;
};
mtbPassword.android.rippleEnabled = true;
mtbPassword.android.rippleColor = Color.RED;
mtbPassword.rightLayout = { view: imgShow, width: 30 };
var btnLogin = new Button({
top: 20,
height: 60,
width: 200,
backgroundColor: Color.create("#00A1F1"),
text: "Login",
onPress: e => {
// If username or password doesn't exist, show error message
let usernameExists = !!mtbUsername.text;
let passwordExists = !!mtbPassword.text;
!usernameExists && (mtbUsername.errorMessage = "Invalid username");
!passwordExists && (mtbPassword.errorMessage = "Invalid password");
}
});
// On Android, height must be given (Refer "Height difference")
if (!isIOS)
mtbUsername.height = TB_HEIGHT_ANDROID;
mtbUsername.ios.clearButtonEnabled = true;
mtbUsername.font = NORMAL_FONT; // Font of editable area
mtbUsername.labelsFont = NORMAL_FONT; // Font of error & hint area
mtbUsername.enableErrorMessage = true;
mtbUsername.keyboardType = KeyboardType.android.TEXTSHORTMESSAGE;
// On Android, height must be given (Refer "Height difference")
if (!isIOS)
mtbPassword.height = TB_HEIGHT_ANDROID;
mtbPassword.ios.clearButtonEnabled = true;
mtbPassword.font = NORMAL_FONT; // Font of editable area
mtbPassword.labelsFont = NORMAL_FONT; // Font of error & hint area
mtbPassword.enableErrorMessage = true;
flWrapper.addChild(mtbUsername);
flWrapper.addChild(mtbPassword);
flWrapper.addChild(btnLogin);
this.layout.addChild(flWrapper);
}
);

Height & Width difference

  • If height isn't specified in iOS, it is considered as the minimum height required. Android always requires height.

  • If width isn't specified, looks tight than required in Android and iOS matches it parent width. -Recall that you can define OS specific styles using rules with UI Editor.

Adjusting Text Size

There are three labels of the size that can be modified.

Unselected Hint Text -Modify labelsFont size proeperty.

JavaScript
JavaScript
mtbUsername.labelsFont = Font.create(Font.DEFAULT, 22, Font.NORMAL);
//Font size >> 22dpi

The font size used in the Material Textboxes can be set to a specific value through the style.xml file. These changes can be observed in the published app, not on emulator.

XML/HTML/SVG
XML/HTML/SVG
<resources>
<style name="SFMaterialTextBoxHintAppearance" parent="TextAppearance.Design.Hint" >
<item name="android:textSize">40dp</item>
</style>
<style name="SFMaterialTextBoxErrorTextAppearance" parent="TextAppearance.Design.Error" >
<item name="android:textSize">10dp</item>
</style>
</resources>

Selected Hint Text Size -Modify style.xml file below SFMaterialTextBoxHintAppearance.

Error Text Size -Modify style.xml file below SFMaterialTextBoxErrorTextAppearance.

Colors

There are total of 4 main colors of MaterialTextBox

  • Color of text shown in default

  • Color of line under the text where user is able to edit

  • Color of hint which appears at the top

  • Color of the cursor (not changeable for both platforms)

Also 3 different states of MaterialTextBox

  • Default state, when user didn't interact with MaterialTextBox

  • Selected state, when is focused on MaterialTextBox

  • Error state, when errorMessage is shown

Changing Colors

Currently some colors are not changeable on both platforms. Here is what you may change:

Tips & Tricks

  • The Android only property, enableErrorMessage is not mandatory to use, but recommended. Not assigning it may cause unexpected results. Enabling error and counter in initialization time that will draw space for those views. By default disabling error messages causes a little bit bounce, in order to handle must be set enable out of the gate then assign.

  • It is not recommended to use flexGrow on both Android and iOS.

  • To change the keyboard appearance when it is touched on MaterialTextBox, refer here.

Known Issue

Currently there is no way to change font size on Android while using labelsFont property. Any given font size parameter will be ignored.