Fundamental Concepts
Property
The property system in NativeScript allows for the exposure of native platform properties to JavaScript components with certain guarantees built-in; for instance:
- it will set properties only after the native platform object is created
- it allows for property definitions with custom value converters
- it auto detects if property values change
- it can suspend updating of values when desired
- it can auto request layout updates when properties change when desired
Within NativeScript, the Property class serves as a fundamental property system, encompassing a straightforward enhancement to the Object.defineProperty function. This class also incorporates supplementary callbacks such as valueChange, valueConverter, and equalityComparer.
Using Property
Adding a new property to a View instance
Add new properties by creating a new Property
with desired options.
class MyButtonBase extends Button {}
export const textProperty = new Property<MyButtonBase, string>({
name: 'text',
defaultValue: '',
affectsLayout: true,
})
Then register the property with the desired class:
textProperty.register(MyButtonBase)
To apply the JS value to the exposed native view, call the setNative
method on the property instance.
[textProperty.setNative](value: string) {
this.nativeView.text = value
}
To gain a comprehensive understanding of how the Property system is employed in NativeScript to expose a native UI component, we recommend reading the informative article titled How to create a NativeScript plugin that uses native iOS & Android views (Part 1) - Label Marquee!. This article provides a practical real-world example that demonstrates the process of developing a NativeScript plugin while utilizing native iOS and Android views.
Adding a new CSS property
To add a new CSS property, use the CssProperty class. It extends the Property class to accept the extra cssName
option. CSS properties are registered with the Style class.
import { CssProperty, Style } from '@nativescript-core'
export const myOpacityProperty = new CssProperty<Style, number>({
name: 'myOpacity',
cssName: 'my-opacity',
defaultValue: 1,
valueConverter: (v) => {
const x = parseFloat(v)
if (x < 0 || x > 1) {
throw new Error(`opacity accepts values in the range [0, 1]. Value: ${v}`)
}
return x
},
})
myOpacityProperty.register(Style)
Subsequently, any instance of the Style class will have myOpacity
property and developers can set it in CSS as follows:
.some-class {
my-opacity: 0.5;
}
Inheritable property
To create a CSS property that can be applied to a view and is inheritable by the children of that view, instanciate the InheritedCssProperty class passing it an PropertyOptions object. Then use the setInheritedValue: (value: U)
method to set the inherited value. The examples of inherited properties are theFontSize
, FontWeight
, Color
, etc.
import { Color, InheritedCssProperty, Style } from '@nativescript-core'
export const selectedBackgroundColorProperty = new InheritedCssProperty<
Style,
Color
>({
name: 'selectedBackgroundColor',
cssName: 'selected-background-color',
equalityComparer: Color.equals,
valueConverter: (v) => new Color(v),
})
selectedBackgroundColorProperty.register(Style)
Shorthand property
To create a CSS property that can be applied using the shorthand syntax rules, use the ShorthandProperty class, passing it the ShorthandPropertyOptions object. For example, instead of setting margin using seperate rules for each side,
.title {
margin-top: 0;
margin-right: 10;
margin-bottom: 0;
margin-left: 10;
}
you can use one rule for all the sides.
.title {
margin: 0 10 0 10;
}
To create a shorthand property, use the CssProperty
class to define all the properties individually as normal. Then return the shorthand with the getter()
method of the ShorthandProperty
class. Here's an example of how the margin shorthand is implemented:
const marginProperty = new ShorthandProperty<
Style,
string | CoreTypes.PercentLengthType
>({
name: 'margin',
cssName: 'margin',
getter: function (this: Style) {
if (
PercentLength.equals(this.marginTop, this.marginRight) &&
PercentLength.equals(this.marginTop, this.marginBottom) &&
PercentLength.equals(this.marginTop, this.marginLeft)
) {
return this.marginTop
}
return `${PercentLength.convertToString(
this.marginTop
)} ${PercentLength.convertToString(
this.marginRight
)} ${PercentLength.convertToString(
this.marginBottom
)} ${PercentLength.convertToString(this.marginLeft)}`
},
converter: convertToMargins,
})
marginProperty.register(Style)
Creating a coercible property
To create a coercible property use the CoercibleProperty class passing it a CoerciblePropertyOptions object.
export const selectedIndexProperty = new CoercibleProperty<
SegmentedBar,
number
>({
name: 'selectedIndex',
defaultValue: -1,
valueChanged: (target, oldValue, newValue) => {
target.notify(<SelectedIndexChangedEventData>{
eventName: SegmentedBar.selectedIndexChangedEvent,
object: target,
oldIndex: oldValue,
newIndex: newValue,
})
},
// in this case the coerce value will change depending on whether the actual number of items
// is more or less than the value we want to apply for selectedIndex
coerceValue: (target, value) => {
let items = target.items
if (items) {
let max = items.length - 1
if (value < 0) {
value = 0
}
if (value > max) {
value = max
}
} else {
value = -1
}
return value
},
valueConverter: (v) => parseInt(v),
})
selectedIndexProperty.register(SegmentedBar)
Subsequently, when assigning a value to the property, invoke the coerce()
method.
[itemsProperty.setNative](value: SegmentedBarItem[]) {
...
selectedIndexProperty.coerce(this);
}
Property System Reference
Property class
The Property class has the following members.
constructor
property = new Property<MyClass, U>(propertyOptions)
enumerable
isEnumerable: boolean = property.enumerable
configurable
isConfigurable: boolean = property.configurable
writable
isWritable: boolean = property.writable
name
propertyName: string = property.name
getDefault
;[property.getDefault]()
setNative
[property.setNative](value){
}
defaultValue
defaultValue: U = property.defaultValue
nativeValueChange
property.nativeValueChange(owner: T, value: U)
isStyleProperty
isStyleProperty: boolean = property.isStyleProperty
get()
propertyValue: U = property.get()
set()
property.set(value: U)
register()
property.register(SomeClass)
isSet()
isPropertySet: boolean = property.isSet(instance: T)
### overrideHandlers()
property.overrideHandlers(options: PropertyOptions<T, U>)
PropertyOptions Interface
name
{
name: 'propertyName'
}
defaultValue
{
defaultValue: someValue
}
affectsLayout
{
affectLayout: true
}
equalityComparer
{
equalityComparer: (x, y) => {
// compare x to y and return boolean
return true
}
}
valueChanged
{
valueChanged: (target: T, oldValue: U, newValue: U) => {}
}
valueConverter
{
valueConverter: (value: string) => {
return someValue
}
}
CssPropertyOptions interface
cssName
{
cssName: 'my-css-property'
}
The property name intended for utilization within a CSS rule.
ShorthandPropertyOptions Interface
{
name: 'newProperty'
}
cssName
{
cssName: 'my-css-property'
}
converter()
{
}
getter()
{
getter: function (this: Style){
return cssValue
}
}
- Previous
- Using Modals
- Next
- Styling