Releases: eclipsesource/tabris-js
Version 2.9.0
iOS 13 Compatibility
Fixed crash on startup when running on iOS 13
Backports
The following properties have been backported from the 3.x branch:
TextInput: "keyboardAppearanceMode"
app: "idleTimeoutEnabled"
Version 3.2.1
This patch release fixes compatibility with cordova plug-ins and a bug in the image scaling logic on Android.
Version 3.2.0
UI
New Widget "CameraView"
Tabris.js 3.2 adds the ability to take images via the devices camera. In order to so so, the tabris.Device
object provides a list of cameras, which can be set on CameraView
to show a live preview feed. To capture
an image, simply call the asynchrenous camera.captureImage()
method, which returns a Blob
containing a JPEG. The existing permissions API can be used to obtain access to the camera.
Minimal Example:
const camera = device.cameras[0];
permission.withAuthorization('camera',
() => camera.active = true,
() => console.log('"camera" permission is required.'),
(e) => console.error(e));
contentView.append(
<Stack stretch>
<CameraView stretchY camera={camera}/>
<Button text='Take picture' onSelect={captureImage}/>
</Stack>
);
async function captureImage() {
const {image} = await camera.captureImage({flash: 'auto'});
// do something with the "image" blob...
}
Blob and BitmapImage as Generic Images
A compressed image (jpeg, png) obtained as a Blob
object - for example via fetch
or Camera
- can now used anywhere the existing API accepts an ImageValue
.
Minimal Example:
const response = await fetch('http://foobar/image.png');
const blob = await response.blob();
contentView.append(<ImageView stretch image={blob}/>);
The same is true for BitmapImage
objects, which can be created from Blob
and ImageData
. That way the the image is already in memory and its size already known before it is used in the UI.
This feature will become much more powerful when the BitmapImage
API is extended in future Tabris.js releases.
New TextInput properties
The messageColor
property lets you control the color of the message
placeholder text independently from the input text.
On Android only keyboardAppearanceMode
can be used to prevent the onscreen keyboard from popping up when a TextInput
is focused, or only when it is focused by touch.
Option to control CollectionView.reveal() animation
Previously it was not possible to control whether a CollectionView.reveal() operation would be executed in an animated fashion or not. Now there is parameter a parameter which allows to control this behavior. The default is to animate the reveal operation.
Data Binding
The JSX/decorators based Data Binding API (TypeScript only) has been extended to allow bindings between widgets and non-widget ("plain") objects, including change detection. Previously this was only possible in a very limited manner. This feature was added to properly support the MVVM (Model-View-ViewModel) programming pattern.
Any instance of a class using the @property
decorator on its properties can be used for bindings. One-way bindings to such an object are declared via JSX. The syntax for this remains unchanged, except that it is now also possible to bind to deeply nested properties:
@component
export class ExampleComponent extends Composite {
@property public myObject: Model;
constructor(properties: Properties<ExampleComponent>) {
super();
this.set(properties).append(
<Stack stretch>
<ProgressBar bind-selection='myObject.someNumber'/>
<TextView bind-text='myObject.otherModel.someString' text='Placeholder'/>
</Stack>
);
}
}
The above example applies values of myObject
properties to ProgressBar
and TextView
properties. If the source values are changed, or the entire myObject
object is replaced, the widget properties are updated accordingly. Should the widget properties change for some other reason the source properties keep their value.
Two-way bindings to plain objects are declared via the @bind
or the @bindAll
decorator (which is a shorthand). The decorators take a parameter object mapping the property of the object to any property of any component-internal child (identified via its id):
@component
export class ExampleComponent extends Composite {
@bindAll({
myText: '#input1.text',
myNumber: '#input2.selection'
})
public model: Model;
constructor(properties: Properties<ExampleComponent>) {
super();
this.set(properties).append(
<Stack>
<TextInput id='input1' text='Fallback Text'/>
<Slider id='input2'/>
</Stack>
);
}
}
This applies values of model
properties to TextInut
and Slieder
properties and vice versa. Of course, if the widget property can't change by itself (i.e. TextInput
's message
instead of text
) this is effectively a one-way binding.
Other
CLI
The tabris init
command can now also create projects with MVVM example code, including unit tests.
New Event Handling Method "triggerAsync"
This method is an asynchronous alternative to trigger()
. When called with await
it pauses for all listener to resolve, including those marked async
. This can be desirable - for example - to propagate errors from async
listeners to the code that triggers the event, or when writing unit tests that need to verify the behavior of asynchronous code.
Internal Change: Property System Reworked
The internal property system has been partially re-written, which may cause minor differences in behavior. Most notably, properties should now behave more consistently regarding value checking and conversion/normalization. Also, properties that take objects/arrays no longer operate with safe copies. Instead they now keep the exact instance that was set (if no conversion is necessary), but makes them immutable if appropriate.
Due to this change Tabris Plug-Ins targeting 3.0 or 3.1 do not work with 3.2. New compatible versions have been released for the following plug-ins:
- tabris-plugin-maps (6.0.0)
- tabris-plugin-firebase (4.0.0)
- tabris-plugin-barcode-scanner (3.0.0)
Documentation Update
The documentation has been slightly revised, most noteably featuring a more structured table of contents in the left sidebar. Also, the presentation of type information in API documents has been tweaked and now always links to the type in question, either within the Tabris.js documentation or to MDN.
Version 2.8.1
v2.8.1 Update Version to 2.8.1
Version 3.1.0
New Permissions API
In order the access features like the devices location or camera, the user has to grant corresponding permissions to the app. Tabris 3.1 introduces the permission
object that allows the app to check and request these permissions at runtime.
FormData support
Tabris.js now provides the FormData
class (including related classes Blob
and File
) as it is specified by the W3C. It can be used in conjunction with fetch()
or XMLHttpRequest
to upload data in the "multipart/form-data" encoding.
Widgets API
New CanvasContext method "drawImage"
Until now the CanvasContext
class implemented by Tabris.js lacked the W3C standard method drawImage
. It has now been added with support for instances of the ImageBitmap
class, which represent uncompressed in-memory images. ImageBitmap
objects are created with the asynchronous createImageBitmap
method using a Blob
(containing a .jpg or .png image), ImageData
or another ImageBitmap
instance as the source.
New property "imageTintColor" on Button
This new property allows to tint the image displayed on a Button widget.
New property "scrollbarVisible" on CollectionView
The property works in the same fashion as scrollbarVisible
on the ScrollView by showing or hiding the scroll bar.
New property "maxChars" on TextInput
Allows to limit the maximum number of characters that the user can enter into a TextInput.
New events "select" and "reselect" on Tab
The new "select"
event on the Tab
fires in tandem with the "select"
event on the parent TabFolder
, while "reselect"
on the Tab
fires when the tab is tapped by the user while it is already visible.
Infinite Animations
The animate
method now allows Infinity
as a value for the repeat
option.
Version 2.8.0
This relase adds some features introduced in Tabris.js 3.0 and 3.1 to Tabris.js 2.x. Like usual it also includes some minor bugfixes.
ImageBitmap and drawImage
Until now the ConvasContext
class implemented by Tabris.js completely lacked the w3c standard method drawImage
. The new drawImage
method takes instances of ImageBitmap
, which represent uncrompressed in-memory images. ImageBitmap
objects can be created with the asynchronous createImageBitmap
method from a Blob
contining a JPEG or PNG image.
ImageBitmap
is a W3C standard and supported by all three Tabris.js 2 platforms.
Widget property "absoluteBounds"
The new property absoluteBounds
provides widget bounds relative to the tabris.ui.contentView
widget instead of the direct parent like bounds
does.
ScrollView properties "scrollXState" and "scrollYState"
The ScrollView adds the properties scrollXState
and scrollYState
which indicate whether the view is currently, dragging, scrolling or in a resting position. Matching change events are available to observe these state changes while in motion.
Version 3.0.1
v3.0.1 Correct cordova versions in FAQ
Version 3.0.0
Adjust console snippet for better stack trace on iOS Unfortunately iOS stack traces skip arrow functions, which is why the cleaned-up stack trace was empty in this snippet. (The trace function falls back to the original stack trace.) Add a intermediate named function to the function so we get a clean trace on iOS. Change-Id: I86db8a64cc4bb7bfa2c2587a701d251608ca91c5
3.0.0-rc1
General
App
The new read-only property debugBuild
can be queried to find out if the app is build in debug mode or release mode as given by the Tabris.js CLI to the tabris build
command.
UI
CollectionView
The CollectionView
no longer provides a select event as it worked unreliable across platforms. Interactions with a cell have to be handled directly by listeners attached to the cell. The new method itemIndex(cell)
may be used to determine the index associated with a cell:
collectionView.createCell = () => {
const cell = new SomeWidget();
cell.onTap(() => {
const index = cell.parent(CollectionView).itemIndex(cell);
// do something with the item...
});
return cell;
}
The method cellByItemIndex(index)
was added to allow the reverse - getting the cell widget currently associated with the given index. Can be null when the cell at the given index is currently not displayed.
TextInput
On Android the TextInput
appearance can now be customized via the new properties style
, and floatMessage
. The new properties allow to support the design style guide outlined in the material design specs.
Picker
The Picker
now allows to show an empty state. This unselected state can be filled with a new message
text property similar to a TextInput
. The empty state has the selectionIndex
of -1
. Setting the selectionIndex
to -1
reverts to the empty state and shows the message
if available.
On Android the Picker
can now be customized via the new properties style
, and floatMessage
similarly to the new properties introduced on TextInput
.
With the introduction of the new style
property on Picker
, the iOS only property fillColor
became redundant and was removed. Previously the fillColor
was required to separate the Android underline colorization from the ios picker background color. Setting the Picker
style
to underline on Android now ignores the background and only applies the borderColor
property.
In addition the font
property (previously available on Widget in Tabris.js 2.x) has now been added on the Picker explicitly. It changed the appearance of the text shown in the Picker box.
ScrollView
The ScrollView
adds the properties scrollXState
and scrollYState
which indicate whether the view is currently, dragging, scrolling or in a resting position. Matching change events are available to observe these state changes while in motion.
StackLayout and StackComposite
The StackComposite
widget introduced in 3.0.0-beta2 was renamed to just Stack
and now respects the layoutData
of its children. Details can be found in the revised Layout documentation.
TabFolder
The TabFolder
has a new property selectionIndex
to set the active Tab
without having an instance of it. This is especially useful when using JSX where the Tab
children are created declaratively and no instance is immediately available.
The badge
and badgeColor
property are now also supported on Android. The expected type of the badge
is now a number
instead of a string.
Widget
The padding
property is now available on all widgets, not just Composite
. It also supports shorthand syntax like [1, 10, 4, 8]
, '1 10 4 8'
or simply 16
, in place of {top: 1, right: 10, bottom: 4, left: 8}
.
The layoutData
preset "fill"
was renamed "stretch"
and presets "stretchX"
and "stretchY"
have been added.
A new property excludeFromLayout
was added that can be set to true
to make the widget not just invisible (like visible = false
) but to also make the layout behave as though the widget does not exist. That way there is no empty space where the widget would have been displayed.
The new property absoluteBounds
provides widget bounds relative to the tabris.contentView
instead of the direct parent like bounds
does.
tabris.ui
The tabris.ui
object which was already deprecated is now completely removed.
TextView
The TextView
alignment value 'center'
was renamed to 'centerX'
.
AlertDialog
The AlertDialog
property texts
has been replaced with a ContentView
object. Any TextInput
instances are appended to it. The ContentView
s layout can not be changed and it will position to TextInput
objects from top to bottom, similar to replaced texts
property.
NavigationView and Actions
Removed properties placementPriority
from Action
and navigationAction
from NavigationView
: These properties are replaced by a new property placement
on the Action
widget. It accepts the values 'default'
(same as placementPriority = 'normal'
), 'overflow'
(same as a placementPriority = 'low'
) and 'navigation'
, which puts the action in the place usually reserved by the drawer icon.
Selector API
Introduced global function $()
as an alias for tabris.contentView.find()
. This especially makes small snippets more readable.
On WidgetCollection
the new only()
method works like first()
, but throws if there is more than one match. It's therefore more secure than first()
when selecting a single widget.
The parent()
method now also takes a selector, returning the nearest parent that matches.
Removed method find()
due to its ambiguous nature. This does not affect composite.find()
which still exists.
JSX Stateless Functional Components (i.e. factories) can now be used as selectors, just like widget constructors can.
JSX
All popups now support JSX, i.e. Popover
, DateDialog
and TimeDialog
, in addition to the already supported AlertDialog
.
Markup elements: JSX elements can now directly contain tags like <b>
and <i>
.
Element <$>
: This is a globally available element that may be used to group widgets (instead of <WidgetCollection>
). It can also contain text, in which case it returns a string.
Almost all layout properties (e.g. left
, top
, centerY
...) now accept true
as an alias (usually for 0
) which allows a shorter syntax in JSX, e.g. <TextView centerY>
instead of <TextView centerY={0}>
.
All layoutData
preset values ('center'
, 'stretch'
, 'centerX'
, 'stretchY'
) can now be used as JSX shorthands, e.g. <TextView stretchY>
instead of <TextView layoutData='stretchY'>
.
TypeScript
Widgets TabFolder
, NavigationView
and CollectionView
are now generic, allowing to narrow down the type of accepted children. The feature already existed on Composite
.
The pseudo-property "jsxProperties" that is used to define which JSX attributes are supported in .tsx
files was renamed to "jsxAttributes". However, due to changes in the type declaration files of tabris this property should rarely be needed anyway.
Developer Experience
Most Developers Guide articles have been re-written for clarity, links fixed, code examples and images added.
Various internal/low-level APIs have been documented for better extendability/hackability.
The tabris module now includes a sub-module 'ClientMock' that can be used to initialize the tabris module outside of a native Tabris environment, e.g. in node. When generating a new (compiled) Tabris 3.x project via the Tabris CLI (or the yeoman generator) the option to generate such a test setup based on mocha is given. This feature is not yet documented in the developers guide.
The global $()
method can be used in the developer console (or CLI with interactive mode) to access any widget (or NativeObject
) via its cid
number. The cid is usually given in log messages/warning.
3.0.0-beta2
UI
StackLayout and StackComposite
The new StackComposite
widget arranges all of its children automatically in one vertical stack, starting from the top. Children may be aligned to the left, right, horizontal center, or stretched. The layoutData
on the children is currently ignored, but will be supported in the final 3.0 release.
Example:
contentView.append(
<StackComposite layoutData='fill' spacing={24} >
<TextView background='red'>lorem</TextView>
<TextView background='green'>ipsum dolor</TextView>
<TextView background='blue'>sit amet</TextView>
</StackComposite>
);
This behavior can be added to most widget that inherit from Composite
(e.g. Page
, ScrollView
) by setting the new layout
property to a StackLayout
instance.
Example:
contentView.append(
<ScrollView layoutData='fill' layout={new StackLayout({alignment: 'stretchX'})} >
<TextView background='red'>lorem</TextView>
<TextView background='green'>ipsum dolor</TextView>
<TextView background='blue'>sit amet</TextView>
</ScrollView>
);
LayoutData Shorthands
In practice widgets often are made to fill their parent completely, or centered on both axes. That behavior can now be achieved with less code by setting layoutData
to 'fill'
or 'center'
instead of an LayoutData
object.
Button Styling
The look-and-feel of the Button
widget can now be adjusted by setting the style
property to "default"
, "elevate"
, "flat"
, "outline"
or "text"
. The default
style will create a platform specific appearance.
Logging
Better Stack Traces
Stack traces have been improved in a number of ways:
- Clean platform-independent format
- Framework internals are filtered out
- Source map support, i.e. corrent line numbers when using TypeScript/JSX. (*)
- cross timer/promise traces - works great in combination with
async
/await
(*) Requires the tabris-cli
version matching this release, install with npm i [email protected] -g
.
The improved stack traces are printed when calling console.trace()
or error.toString()
.
Better warnings
Warnings printed by the framework do now generally include the source objects type and id, and also the file/line number in the application that caused the warning. The phrasing of many warnings has also been revised to be clearer and contain more relevant information.
Improved console.dirxml()
The dirxml
method was already introduced in beta 1 as a convenient way to print the applications widget tree (or parts of it), but it only displayed the widget type and id. Now it also prints essential information like widget bounds, selection status, text value, etc.
In addition, dirxml
can now also be called with localStorage
to inspects its content.
localStorage
The localStorage
now also implements key
and length
as specified in the W3C standard. This allows programmatically inspecting its content without knowing the keys associated with the contained items.
JSX and TypeScript improvements
New object literals for property types
Various properties in Tabris.js used to accept only CSS-like strings, which are short but error-prone. Since the IDE and TypeScript compiler can not know what format a string is supposed to have, no auto-complete could be provided. As a result typos would not become obvious until the code was executed.
The new object literals for font, color and gradient types have full auto-completion and TypeScript support while staying 100% compatible with the old string formats. While layoutData
was already an object literal, it now allows objects to express the constraint values, e.g. left
and top
.
Each type also has a matching JavaScript class that is used as the default format by the framework. For example:
widget.background = {red: 255, green: 0, blue: 0};
// or
widget.background = '#ff0000';
// are normalized to a `Color` instance, i.e.
console.log(widget.background instance Color); // true
console.log(widget.background.toString()); // #ff0000
console.log(widget.background.red); //255
JSX support for AlertDialog
As with ActionSheet
in beta 1, AlertDialog
can now be written as JSX. That allows for more readable code when a the dialog contains TextInput
elements:
const dialog = AlertDialog.open(
<AlertDialog title='Sign-in required' buttons={{ok: 'Sign-in', cancel: 'Cancel'}}>
Provide sign-in credentials to access your personalized content.
<TextInput message='Username' />
<TextInput type='password' message='Password' />
</AlertDialog>
);
const {texts, button} = await dialog.onClose.promise();
The new static open
method that serves as a convenient shorthand to create a very simple message dialog:
await AlertDialog.open('Comment saved').onClose.promise();
textView.text = 'Dialog closed';
Documentation
API documentation has been tweaked to improve readability and provide a short, informative overview for each class, method and property at first glance.
Breaking changes
Object 'ui' removed
The 'ui' object used to be a pseudo-widget that contained the main ContentView
, the Drawer
instance and various pseudo-widgets like StatusBar
. This proved to be confusing and sometimes inconvenient, so all objects held by ui
are now available as direct imports from the tabris
module:
Old:
import {ui, TextView} from 'tabris';
ui.contentView.append(new TextView());
New:
import {contentView, TextView} from 'tabris';
contentView.append(new TextView());
Removed 'installPatch' method
The method app.installPatch
allowed to patch a Tabris.js application on the fly without installing a complete new version. This API was always marked as experimental and is now removed again. It encouraged a risky practice (bypassing store policies) and was never widely used.
As a replacement the app.reload
method now takes an URL parameter, allowing to execute any local or remote JavaScript in place of the packaged code. This, combined with fetch
and the fs
file system API would theoretically allow implementing this feature in-app, if desired.
Property 'backgroundImage' merged in to background
The property background
was already supporting both colors and gradients, so it was an obvious move to make it accept images as well, eliminating the need for a separate backgroundImage
property.