Svelte Native (https://svelte-native.technology/) enables implementing Android and iOS applications using Svelte by building on top of NativeScript (https:// nativescript.org/). It’s a big topic. The learning path includes how to structure a Svelte Native app, use provided components, use provided layout mechanisms, implement page navigation, use NativeScript-specific styling and themes, use third-party libraries, and integrate with device capabilities. We will only scratch the surface here, but we will cover enough to enable you to start building mobile apps.
NativeScript uses XML syntax (with custom elements), CSS, and JavaScript/ TypeScript to create apps that run on Android and iOS devices. It renders native components rather than web views. NativeScript was created and is maintained by Telerik Corp., which was acquired by Progress Software in 2014.
NativeScript can be used without any web framework. In addition, there are integrations of several popular web frameworks with NativeScript. The NativeScript team supports Angular and Vue integrations, and the community at large supports React and Svelte implementations.
Svelte Native provides a thin layer over the NativeScript API that makes it relatively easy to remain compatible with future NativeScript versions. It was created by David Pershouse, who goes by halfnelson on GitHub and @halfnelson_au on Twitter.
The official Svelte Native tutorial and API documentation can be found on the Svelte Native website: https://svelte-native.technology/.
We will cover all the provided components at a high level. Then we will look at the steps for getting started creating Svelte Native applications. Finally, we will walk through a few example apps, including a basic Hello World app, one that demonstrates nearly all the provided components, and one that uses an add-on library to implement a hamburger (side-drawer) menu.
NativeScript provides many components, and you can build more by combining them. A list of provided components can be found at https://docs.nativescript.org/ ui/overview.
Svelte Native exposes all of these components as DOM elements to Svelte, and they are used as alternatives to the HTML elements used in Svelte apps. They are globally available and so can be used in custom Svelte components without importing them. Documentation on these Svelte components can be found at https://svelte-native .technology/docs.
The names of the provided NativeScript components are camel case and begin with a capital letter. However, the Svelte components that wrap them have names that begin with a lowercase letter. This distinguishes them from custom Svelte components whose names must start with an uppercase letter. For example, instead of <Label
text="Hello"
/>
you would write <label
text="Hello"
/>
. We will use the Svelte Native names here when discussing them.
The following sections describe each of the provided components at a high level. Section 21.7 provides examples of using each component.
The Svelte Native components that display data are listed in table 21.1 after the closest HTML element to which they correspond.
The label
component can specify its content using a text
attribute, text content, or a formattedString
child with span
children.
The activityIndicator
component displays a platform-specific spinner to indicate that some activity, such as waiting for a response from an API service, is occurring.
The listView
component displays a scrolling list of items. Its items
attribute is set to an array of values that can be any JavaScript type. To specify how each item should be rendered, include a Template
child element whose content is components that render the item. To take action when an item is clicked, set on:itemTap
to a function.
The htmlView
and webView
components are used to render a string of HTML. Using webView
is preferred because htmlView
has a very limited ability to apply CSS styling. The HTML string for an htmlView
component is specified with an html
attribute. The HTML string for a webView
component is specified with a src
attribute whose value can be an HTML string, a URL, or a path to an HTML file. Often instances of the webView
component must be given a height because that isn’t calculated based on its content. HTML from untrusted sources should be sanitized before it’s passed to a WebView
component because it is capable of executing JavaScript code.
The Svelte Native components that allow user input are listed in table 21.2 after the closest HTML element to which they correspond.
The searchBar
component is similar to the textField
component, but it adds a magnifying glass icon to the left of the input.
Components for actions include actionBar
, actionItem
, and navigationButton
.
An actionBar
is a toolbar that is displayed at the top of the screen. It typically contains a title and can also contain actionItem
and navigationButton
components.
actionItem
components are buttons with platform-specific icons and positioning. An example is the iOS share icon that looks like a square with an arrow pointing up. For Android, the icon is identified by one of the R.drawable
constants documented at https://developer.android.com/reference/android/R.drawable. For iOS, the icon is one of the SystemItem
constants documented at https://developer.apple.com/documentation/uikit/uibarbuttonitem/systemitem.
Functions that render a dialog include action
, alert
, confirm
, login
, and prompt
. Table 21.3 describes the contents of each kind of dialog.
Title, message, username input, password input, cancel button, and OK button |
|
Provided components that organize their child components in a certain way are listed in table 21.4 after the closest CSS property value to which they correspond.
A page
component can optionally contain an actionBar
component, and it can only contain one top-level layout component.
absoluteLayout
requires its children to specify their absolute location with left
and top
attributes.
dockLayout
positions its children on a given side of the screen: left, right, top, or bottom. The example in listing 21.1 shows how to create the classic layout with a header, footer, and left nav (see figure 21.1). Note that the header and footer children must come before the left child in order for them to span all the way across the display.
<page> <dockLayout> <label class="header big" dock="top" text="Header" /> <label class="footer big" dock="bottom" text="Footer" /> <label class="nav big" dock="left" text="Nav" /> <stackLayout> <label text="Center child #1" /> <label text="Center child #2" /> </stackLayout> </dockLayout> </page> <style> .big { color: white; font-size: 24; padding: 20; } .footer { background-color: purple; border-top-width: 3; } .header { background-color: red; border-bottom-width: 3; } .nav { background-color: green; border-right-width: 3; } stackLayout { background-color: lightblue; padding: 20; } </style>
flexboxLayout
implements most of CSS flexbox. Elements that use it can be styled using standard flexbox CSS properties. In addition, it supports the following attributes:
justifyContent
with values stretch
(default), flex-start
, flex-end
, center
, and baseline
alignItems
with values stretch
(default), flex-start
, flex-end
, center
, space-between
, and space-around
alignContent
with the same values as justifyContent
only affects containers with more than one line (rarely used)
flexDirection
with values row
(default), column
, row-reverse
, and column-reverse
flexWrap
with values nowrap
(default), wrap
, and wrap-reverse
Child elements of flexboxLayout
can specify the attributes alignSelf
, flexGrow
, flexShrink
, flexWrapBefore
, and order
.
The gridLayout
element organizes its children into cells inside rows and columns. A cell can span more than one row and/or column. This is not related to CSS grid layout. The row
and column
attributes, whose values are comma-separated lists, specify the number of rows and columns, the height of each row, and the width of each column.
There are three options for row and column sizes: an absolute number, auto
, or *
. Specifying auto
makes it as small as possible to accommodate everything in that row or column. Specifying *
makes it as large as possible after satisfying the requirements of the other rows or columns. Preceding *
with a number acts as a multiplier. For example, columns="100,2*,*"
means that the first column is 100 device-independent pixels (DIPs) wide, the second column is 2/3 of the remaining space, and the third column is 1/3 of the remaining space.
Child elements of gridLayout
specify their grid position with row
and column
attributes. They can also specify rowSpan
and colSpan
attributes to span more than one row and/or column.
stackLayout
is the most basic of the supported layouts. It simply lays out its children vertically (default) or horizontally. To lay out children horizontally, add the attribute orientation="horizontal"
.
wrapLayout
is similar to stackLayout
, but children wrap to the next row or column when space is exhausted. It differs from stackLayout
in that its default orientation is horizontal. To lay out children vertically, add the attribute orientation=
"vertical"
. To specify a fixed width and/or height for each child, add the itemWidth
and/or itemHeight
attributes.
While not exactly being a layout, the scrollView
component does act as a container for other components. It creates a fixed size area where its child components can be scrolled vertically and horizontally.
Components for creating navigation tabs include tabs
(displayed at the top of the screen) and bottomNavigation
(displayed at the bottom). The tabs
component is more full-featured than bottomNavigation
. For example, it supports switching tabs by swiping left or right across the content area below the tabs.
Both tabs
and bottomNavigation
have children that are tabStrip
and tabContentItem
components. The children of tabStrip
components are tabStripItem
components, which represent each of the tabs. The tabContentItem
components hold the components to render when the corresponding tab is selected. The children of tabContentItem
components are typically layout components containing many children.
navigationButton
components are buttons for platform-specific functionality, such as the back button.
Another way to support page navigation is to use a side drawer. This is not provided in Native Script. It is an add-on that can be found in the Native Script UI library that is described in section 21.8. See the RadSideDrawer
component.
The easiest way to get started with Svelte Native is to use the online REPL at https:// svelte-native.technology/repl. This is similar to the Svelte REPL. It allows writing, testing, and saving (as GitHub Gists) Svelte Native apps without installing anything. However, unlike the Svelte REPL, it cannot currently download an app, show a list of saved REPL sessions, or recall them.
Another option is to use the online Playground at https://play.nativescript.org/. This has the same options as the Svelte Native REPL, but it also supports projects using any kind of NativeScript project including Angular, Vue, React, Svelte, plain JavaScript, and plain TypeScript.
Both the Svelte REPL and Playground will prompt you to install the NativeScript Playground and NativeScript Preview apps. Do this on all devices to be used for testing apps. For Android, search the Google Play store. For iOS, search the iOS App Store.
To run your app in NativeScript Playground on a device, click the Preview button in the upper right or click QR Code at the top. Both will display a QR code in a dialog. Scan this using the NativeScript Playground app on the devices. It will launch the NativeScript app inside the NativeScript Preview app.
Like the Svelte and Svelte Native REPLs, NativeScript Playground allows you to create an account, log in, and save projects that can be recalled later. To see and select previously saved projects, click Projects in the top nav bar.
After making code changes, click Save at the top. The app will reload on all the devices that have scanned the QR code of the app.
The left side of the Playground includes a file explorer and a palette of components. Component icons can be dragged into code to add an instance of them.
The bottom area has a Devices tab that lists the devices being used. Any number of devices can be tested simultaneously.
For serious development of a Svelte Native app, you will want to create a project where all the required files are available locally and your preferred editor or IDE can be used. To do this, follow these steps:
Install the NativeScript command-line interface (CLI) globally on your computer by entering npm
install
-g
nativescript
.
Verify installation by entering tns
, which outputs help (“tns” stands for Telerik NativeScript).
Create a new app by entering npx
degit
halfnelson/svelte-native-template
app-name.
Tap Scan QR Code and point the camera at the QR code. The first time this is done, it will ask you to install the NativeScript Preview app if it is not already installed. Press Install to proceed.
The NativeScript Preview app will launch and display the app. Before making modifications, this will just display a rocket icon followed by the text “Blank Svelte Native App”.
On your computer, modify the app. Changes will live reload on the phone.
Live reload is very fast, taking only about three seconds for a small app.
Another option to consider, not covered here, is the Electron app NativeScript Sidekick. For information on this, see the NativeScript blog: http://mng.bz/04pJ.
Here is a basic Hello World Svelte Native app. Simply modify app/App.svelte
to match the following.
<script> let name = 'World'; </script> <page> <actionBar title="Hello World Demo" /> <stackLayout class="p-20"> ❶ <flexboxLayout> <label class="name-label" text="Name:" /> <textField class="name-input" bind:text={name} /> </flexboxLayout> <label class="message" text="Hello, {name}!" /> </stackLayout> </page> <style> .message { color: red; font-size: 50; ❷ } .name-input { font-size: 30; flex-grow: 1; } .name-label { font-size: 30; margin-right: 10; } </style>
❶ “p-20” is a provided CSS class that sets padding to 20.
❷ Note that no units are specified on CSS sizes.
Svelte Native apps can be run in an emulator for a specific device. This requires installing a lot of software that is specific to Android and iOS. For details on this, see https://svelte-native.technology/docs#advanced-install.
NativeScript supports a large subset of CSS selectors and properties. It also supports some NativeScript-specific CSS properties. All of these are documented at https:// docs.nativescript.org/ui/styling.
CSS rules that should affect the entire application are placed in app/app.css
. By default, this imports ~nativescript-theme-core/css/core.css
and ./font-awesome .css
(for icons).
Note nativescript-theme-core
is deprecated. Svelte Native still uses this by default and it works for now. See https://docs.nativescript.org/ui/theme for details.
Svelte Native component names are treated as custom element names. They can be used as CSS selectors for styling all instances of the component.
For CSS property values that represent a size, when no unit is specified, the value represents device-independent pixels (DIPs). This is the recommended unit to use. A “px” suffix can be added to size values for plain pixels that vary across devices, but using this unit is discouraged. Percentages with a “%” suffix are also supported.
Other supported CSS features include
Custom fonts in TTF or OTF format found in the app/fonts
directory
Importing a CSS file from another with @import
url('file-path')
Some CSS shorthand properties are not supported. For example, instead of border:
solid
red
3px;
, use border-color:
red;
border-width:
3px;
. The border-style
property is not supported. All borders are solid.
The outline
shorthand property and all CSS properties whose names begin with outline-
are not supported.
To change the style of text within a block of text, use the formattedString
element with span
elements as children. Each span
can specify different styling. The formattedString
element must be a child of a component that supports text content such as button
, label
, textField
, and textView
. The following listing shows an example of using formattedString
.
<page> <stackLayout class="p-20"> <label class="panagram" textWrap="true"> ❶ <formattedString> <span class="fox">The quick brown fox</span> ❷ <span text=" jumps over " /> ❸ <span class="dog">the lazy dog</span> ❹ <span text="." /> </formattedString> </label> </stackLayout> </page> <style> .panagram { font-size: 30; } .fox { color: red; font-weight: bold; } .dog { color: blue; font-style: italic; } </style>
❶ A panagram is a sentence that contains all the letters of the alphabet.
❷ This text will be red and bold.
❸ This text will be black and normal.
❹ This text will be blue and italic.
Components can be styled by defining CSS classes that match their name. For example, the following makes all label
components blue and bold:
label { color: blue; font-weight: bold; }
NativeScript provides a set of predefined CSS classes that can be applied to NativeScript components for common styling needs. These are conveniences that can be used in place of explicitly applying CSS properties.
These predefined classes are appropriate for applying to label
components:
The following snippet is an example of using a predefined class.
<label class="h1" text="This is BIG!" />
There are many provided CSS classes that specify the desired padding and margin (see table 21.5). In these class names, #
is a placeholder for a number, which must be one of 0
, 2
, 5
, 10
, 15
, 20
, 25
, or 30
.
There are several provided CSS classes for styling forms and form elements such as buttons. For details, see the NativeScript documentation for forms (https://docs .nativescript.org/ui/theme#forms) and for buttons (https://docs.nativescript.org/ui/ theme#buttons).
The equivalent of the HTML horizontal rule <hr>
element is to use an empty stackLayout
with the hr
class, as follows:
<stackLayout class="hr" />
To overlay the default horizontal rule with a thicker, colored rule, add CSS properties to the hr
CSS class as follows:
.hr { --size: 10; height: var(--size); border-color: green; border-width: calc(var(--size) / 2); }
For more details on NativeScript styling, see https://docs.nativescript.org/ui/styling.
NativeScript supports styling themes through a large set of CSS classes. To install a default theme along with 11 more themes, enter npm
install
@nativescript/theme
. Additional themes not provided by NativeScript can also be installed. Each theme simply overrides the predefined CSS classes. These support light and dark modes.
To use the default theme, edit app/app.css
and replace the import of core.css
with the following:
@import '~@nativescript/theme/css/core.css'; @import '~@nativescript/theme/css/default.css';
To use one of the other included themes, replace default
in the second line above with one of aqua
, blue
, brown
, forest
, grey
, lemon
, lime
, orange
, purple
, ruby
, or sky
.
Up until now, we’ve been using the Travel Packing app as our example, but we’re going to switch gears here in the interest of brevity. The Svelte Native app in this section provides examples of using nearly all of the components included in NativeScript. It provides a good jumping off point for implementing your own Svelte Native applications.
The app has three pages, each presenting examples of components in a given category:
The first category is components that display information. This includes the label
, webView
, image
, and progress
components.
The second category is components that accept user input. This includes textField
, textView
, switch
, segmentedBar
, datePicker
, timePicker
, listView
, listPicker
, and slider
.
The third category is functions that render dialogs and components used for search queries. This includes the functions login
, prompt
, action
, and confirm
. It also includes the searchBar
and activityIndicator
components.
Page navigation is configured in App.svelte
(see figure 21.2).
<script> import DisplayComponents from './DisplayComponents.svelte'; ❶ import InputComponents from './InputComponents.svelte'; import OtherComponents from './OtherComponents.svelte'; function onTapDelete() { console.log('App.svelte onTapDelete: entered'); } function onTapShare() { console.log('App.svelte onTapShare: entered'); } </script> <page> <actionBar title="Svelte Native Demo"> ❷ <!-- button with upload icon --> <actionItem on:tap="{onTapShare}" ios.systemIcon="9" ios.position="left" android.systemIcon="ic_menu_share" android.position="actionBar" /> <!-- button with trash can icon --> <actionItem on:tap="{onTapDelete}" ios.systemIcon="16" ios.position="right" android.systemIcon="ic_menu_delete" text="delete" android.position="popup" /> </actionBar> <tabs> ❸ <tabStrip> <tabStripItem> <label text="Display" /> <image src="font://" class="fas" /> ❹ </tabStripItem> <tabStripItem> <label text="Input" /> <image src="font://" class="far" /> ❺ </tabStripItem> <tabStripItem> <label text="Other" /> <image src="font://" class="fas" /> ❻ </tabStripItem> </tabStrip> <tabContentItem> ❼ <DisplayComponents /> </tabContentItem> <tabContentItem> <InputComponents /> </tabContentItem> <tabContentItem> <OtherComponents /> </tabContentItem> </tabs> </page> <style> tabStrip { --tsi-unselected-color: purple; --tsi-selected-color: green; background-color: lightblue; highlight-color: green; } tabStripItem { color: var(--tsi-unselected-color); /* for icon */ } tabStripItem > label { color: var(--tsi-unselected-color); /* for text */ } tabStripItem:active { color: var(--tsi-selected-color); /* for icon */ } tabStripItem:active > label { color: var(--tsi-selected-color); /* for text */ } </style>
❶ These three imports are for the “page” components.
❷ The actionBar component is rendered at the top of the screen.
❸ The tabs component is rendered below the actionBar. It contains one tab for each page of the app.
❻ This is a magnifying glass icon.
❼ There is one tabContentItem for each tabStripItem. Each of these render a page of the app.
Note The image
elements in listing 21.4, such as the monitor, keyboard, and magnifying glass icons, use FontAwesome icons. To find the hex code for a given icon, search for them at https://fontawesome.com/icons. For example, searching for “house” and clicking the “home” icon shows that its code is f015
and its class is fas
. The CSS classes fab
, far
, and fas
are defined in app/fontawesome.css
. The CSS class fa-home
is not defined by default in Svelte Native.
The following app-wide CSS styling is added to the existing app.css
file.
button { background-color: lightgray; border-color: darkgray; border-width: 3; border-radius: 10; font-weight: bold; horizontal-align: center; /* to size according to content */ padding: 10; } label { color: blue; font-size: 18; font-weight: bold; } .plain { color: black; font-size: 12; font-weight: normal; } .title { border-top-color: purple; border-top-width: 5px; color: purple; font-size: 20; font-weight: bold; font-style: italic; margin-top: 10; padding-top: 10; }
This app uses several Svelte stores in order to share data across components. They are all defined in stores.js
.
import {derived, writable} from 'svelte/store'; export const authenticated = writable(false); export const backgroundColor = writable('pink'); ❶ export const favoriteColorIndex = writable(0); export const firstName = writable(''); // Not a store. export const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple']; async function evaluateColor(color) { ❷ if (color === 'yellow') { alert({ title: 'Hey there!', message: 'That is my favorite color too!', okButtonText: 'Cool' }); } else if (color === 'green') { const confirmed = await confirm({ title: 'Confirm Color', message: 'Are you sure you like that color?', okButtonText: 'Yes', cancelButtonText: 'No' }); if (!confirmed) favoriteColorIndex.set(0); ❸ } } export const favoriteColor = derived( ❹ favoriteColorIndex, index => { const color = colors[index]; evaluateColor(color); return color; } );
❶ This is used as the background color of all the pages. It is pink when the user has not logged in. It is light green when they have.
❷ This function is called by the favoriteColor derived store defined below.
❸ This sets the favorite color to “red” if the user doesn’t like “green”.
❹ This is a derived store based on the favoriteColorIndex store. It updates its value to a color name every time the value of the favoriteColorIndex store changes.
The page defined by the DisplayComponents
component provides examples of components that display information (see figure 21.3).
<script> import {backgroundColor} from './stores'; const myHtml = ` ❶ <div> <span style="color: red">The quick brown fox</span> <span>jumps over</span> <span style="color: blue">the lazy dog</span> </div> `; let progressPercent = 0; function startProgress() { ❷ progressPercent = 0; const token = setInterval(() => { progressPercent++; if (progressPercent === 100) clearInterval(token); }, 10); } </script> <scrollView> <stackLayout backgroundColor={$backgroundColor} class="p-20" > <label class="title" text="label" /> <label class="plain" text="some text" /> <label class="title" text="label with formattedString" /> <label class="panagram" textWrap="true"> <formattedString> ❸ <span class="fox">The quick brown fox</span> <span text=" jumps over " /> <span class="dog">the lazy dog</span> <span text="." /> </formattedString> </label> <label class="title" text="image" /> <wrapLayout class="image-frame"> ❹ <image src="~/svelte-native-logos.png" stretch="aspectFit" /> ❺ </wrapLayout> <label class="title" text="webView" /> <webView src="<h1>I am a webView.</h1>" /> <webView src={myHtml} /> <webView style="height: 300" src="https://svelte-native.technology/" />❻ <label class="title" text="progress" /> <button on:tap={startProgress}>Start Progress</button> <progress class="progress" maxValue={100} value="{progressPercent}" /> </stackLayout> </scrollView> <style> .dog { color: blue; font-style: italic; } .fox { color: red; font-weight: bold; } .image-frame { background-color: white; padding: 10; } .panagram { background-color: linen; color: black; font-size: 26; font-weight: normal; margin-bottom: 20; padding: 20; } progress { color: red; margin-bottom: 10; scale-y: 5; /* changes height */ } webView { border-color: red; border-width: 1; height: 50; ❼ } </style>
❶ This is displayed in a webView below.
❷ Tapping the “Start Progress” button causes this to be called. It animates the value of the progress bar.
❸ This renders a string where runs of text can have different styling.
❹ The CSS padding property doesn’t affect image components. This is a workaround to add padding to an image.
❺ Svelte Native treats ~ at the beginning of a file path as an alias to the app directory. This image can be found at https://github.com/mvolkmann/svelte-native-components/blob/master/app/svelte-native-logos.png.
❻ The rendered page can be scrolled inside the webView component.
❼ No webView content will be visible without this.
The page defined by the InputComponents
component provides examples of components that gather user input (see figure 21.4).
<script> import {Template} from 'svelte-native/components' import { backgroundColor, colors, favoriteColor, favoriteColorIndex, firstName } from './stores'; const temperatures = ['Cold', 'Warm', 'Hot']; let birthday = new Date(1961, 3, 16); let likeRunning = false; let reason = ''; let stars = 3; let temperatureIndex = 1; let quittingTime = new Date(); quittingTime.setHours(17); quittingTime.setMinutes(0); quittingTime.setSeconds(0); function formatDate(date) { ❶ if (!date) return ''; let month = date.toLocaleDateString('default', {month: 'long'}); const index = month.indexOf(' '); if (index) month = month.substring(0, index); return `${month} ${date.getDate()}, ${date.getFullYear()}`; } function formatTime(date) { ❷ if (!date) return ''; let hours = date.getHours(); const amPm = hours < 12 ? 'AM' : 'PM'; if (hours >= 12) hours -= 12; let minutes = date.getMinutes(); if (minutes < 10) minutes = '0' + minutes; return `${hours}:${minutes} ${amPm}`; } function onFavoriteColor(event) { $favoriteColorIndex = event.value; // odd that this is an index } function onTapColor(event) { $favoriteColorIndex = event.index; } function starChange(event) { stars = Math.round(event.value); ❸ } </script> <scrollView> <stackLayout backgroundColor={$backgroundColor} class="p-20" > <!-- like HTML <input type="text"> --> <label class="title" text="textField" /> <wrapLayout> <label text="First Name" /> <textField class="first-name" hint="first name" bind:text={$firstName} /> <label class="plain" text="Your first name is {$firstName}." /> </wrapLayout> <!-- like HTML <textarea> --> <label class="title" text="textView" /> <wrapLayout> <label text="What would you say you do here?" /> <textView class="reason" hint="reason for being" bind:text={reason} /> <label class="plain" text="Your reason for being is {reason}." textWrap="true" /> </wrapLayout> <!-- like HTML <input type="checkbox"> --> <label class="title" text="switch" /> <wrapLayout> <label text="Like Running?" /> <switch bind:checked={likeRunning} /> <label class="plain" text="You{likeRunning ? '' : ' do not'} like running." /> </wrapLayout> <!-- like HTML <input type="radio"> --> <label class="title" text="segmentedBar" /> <segmentedBar bind:selectedIndex={temperatureIndex} selectedBackgroundColor="yellow" > <segmentedBarItem title="Cold" /> <segmentedBarItem title="Warm" /> <segmentedBarItem title="Hot" /> </segmentedBar> <segmentedBar ❹ bind:selectedIndex={temperatureIndex} selectedBackgroundColor="yellow" > {#each temperatures as temp} <segmentedBarItem title={temp} /> {/each} </segmentedBar> <label class="plain" text="You are feeling {temperatures[temperatureIndex]}." /> <!-- like HTML <input type="date"> --> <label class="title" text="datePicker" /> <wrapLayout> <label text="Birthday" /> <datePicker bind:date={birthday} /> <label class="plain" text="You selected {formatDate(birthday)}." /> </wrapLayout> <!-- like HTML <input type="time"> --> <label class="title" text="timePicker" /> <wrapLayout> <label text="Quitting Time" /> <timePicker bind:time={quittingTime} /> <label class="plain" text="You will quit at {formatTime(quittingTime)}." /> </wrapLayout> <!-- like HTML <ul>, <ol>, or <select> --> <label class="title" text="listView" /> <wrapLayout> <listView items={colors} on:itemTap={onTapColor}> ❺ <Template let:item={color}> <label class="list" class:selected={color === $favoriteColor} ❻ text="One of the colors is {color}." /> </Template> </listView> </wrapLayout> <label class="plain" text="You selected {$favoriteColor}." /> <!-- like HTML <select> --> <label class="title" text="listPicker" /> <wrapLayout> <label text="Favorite Color" /> <listPicker items={colors} selectedIndex={$favoriteColorIndex} on:selectedIndexChange={onFavoriteColor} /> <label class="plain" text="You selected {$favoriteColor}." /> </wrapLayout> <!-- like HTML <input type="range"> --> <label class="title" text="slider" /> <wrapLayout> <label text="Stars" /> <slider minValue={1} maxValue={5} value={stars} ❼ on:valueChange={starChange} /> <label class="plain" text="You rated it {stars} stars" /> </wrapLayout> </stackLayout> </scrollView> <style> .first-name { width: 100%; } .list { border-color: blue; border-width: 1; margin: 0; } .reason { width: 100%; } segmentedBar { color: red; margin-bottom: 10; margin-top: 10; } .selected { background-color: lightgreen; } </style>
❶ This formats a Date object as a string like “April 16, 1961”.
❷ This formats a Date object as a string like “5:00 PM”.
❸ This is an attempt to get the “stars” slider to snap to an integer value, but it does not work.
❹ This shows another way to provide items to the segmentedBar component. The API docs show passing an array of items using the “items” prop, but that does not work. The children must be segmentedBarItem components.
❺ This acts like an HTML select element when there is an on:itemTap handler.
❻ This is not reevaluated when the value of the favoriteColor store changes, so the presence of the selected CSS class is not updated.
❼ The slider position doesn’t change when the starChange function changes the value of stars. See the issue at https://github.com/halfnelson/svelte-native/issues/128.
The page defined by the OtherComponents
component provides examples of components that render a dialog or prompt for a search query (see figure 21.5).
|
|
When the Login button is tapped, the dialog in figure 21.6 is displayed.
If the user enters a correct username and password, the background of all the pages changes from pink to light green to indicate that they have been authenticated (see figure 21.7).
|
|
When the Prompt for First Name button is tapped, the dialog in figure 21.8 is displayed.
When the Pick a Color button is tapped, the dialog in figure 21.9 is displayed.
|
|
After entering part of a color name in the search input, a dialog like the one in figure 21.10 is displayed.
Listing 21.9 shows the code for OtherComponents
that implements the preceding functionality.
<script> import {login} from 'tns-core-modules/ui/dialogs' import { authenticated, backgroundColor, colors, favoriteColor, favoriteColorIndex, firstName } from './stores'; let busy = false; let query = ''; // Should this be handled in stores.js? $: $backgroundColor = $authenticated ? 'lightgreen' : 'pink'; async function getFirstName() { const res = await prompt({ title: 'First Name', message: 'Please tell me your first name.', okButtonText: 'Here it is', cancelButtonText: 'I will not share that' }); if (res.result) $firstName = res.text; } function onSearchSubmit() { busy = true; setTimeout(async () => { // to demonstrate activityIndicator component // The event object passed to this does not contain the search text // or any interesting properties. const q = query.toLowerCase(); const matches = colors.filter(color => color.includes(q)); busy = false; await alert({ title: 'Color Matches', message: matches.length ? matches.join(', ') : 'no matches', okButtonText: 'Close' }); query = ''; // reset }, 1000); } async function pickColor() { const NONE = 'No Thanks'; const choice = await action('Pick a color', NONE, colors); if (choice !== NONE) { $favoriteColorIndex = colors.findIndex(c => c === choice); } } async function promptForLogin() { const res = await login({ title: 'Please Sign In', message: 'This will unlock more features.', userNameHint: 'username', passwordHint: 'password', okButtonText: 'OK', cancelButtonText: 'Cancel', }); if (res.result) { // Authenticate the user here. $authenticated = res.userName === 'foo' && res.password === 'bar'; if (!$authenticated) { alert({ title: 'Login Failed', message: 'Your username or password was incorrect.', okButtonText: 'Bummer' }); } } } </script> <scrollView> <stackLayout backgroundColor={$backgroundColor} class="p-20" > <label class="title" text="login" /> <wrapLayout> {#if $authenticated} <button on:tap={() => $authenticated = false}>Logout</button> {:else} <button on:tap={promptForLogin}>Login ...</button> {/if} </wrapLayout> <label class="title" text="prompt" /> <button on:tap={getFirstName}>Prompt for First Name</button> <label class="plain" text="Your first name is {$firstName}." /> <label class="title" text="action" /> <button on:tap={pickColor}>Pick a Color</button> <label class="plain" text="You selected {$favoriteColor}." /> <label class="title" text="searchBar" /> <!-- Using gridLayout to position activityIndicator over searchBar. --> <gridLayout rows="*"> <searchBar hint="Enter part of a color name." bind:text={query} on:submit={onSearchSubmit} row="0" /> <!-- The activityIndicator height and width attributes control the allocated space. But the size of the spinner cannot be changed, only the color. --> <activityIndicator busy={busy} row="0"/> </gridLayout> </stackLayout> </scrollView> <style> activityIndicator { color: blue; } searchBar { margin-bottom: 10; } </style>
NativeScript UI is a collection of NativeScript components that are not included by default. They include RadAutoCompleteTextView
, RadCalendar
, RadChart
, RadDataForm
, RadGauge
, RadListView
, and RadSideDrawer
.
To use these components in a Svelte Native app, first create the app using npx
degit
halfnelson/svelte-native-template
app-name. Then see the instructions on the NativeScript UI GitHub page at https://github.com/halfnelson/svelte-native-nativescript-ui.
RadListView
is a particularly popular component. It renders a list of items with animations and support for many gestures including “pull to refresh” and “swipe actions.”
Another popular component is RadSideDrawer
. This provides a drawer-based side nav with a hamburger menu that is used for page navigation. Let’s walk through an example that uses this.
To prepare a Svelte Native app for using a RadSideDrawer
component, do the following:
Our app will have two pages named “About” and “Hello”. The About page merely describes the app. The Hello page allows the user to enter a name, and it renders a greeting message.
The side drawer contains the page names. To display the side drawer, click the hamburger icon in the upper left of a page, or drag right from the left edge of the screen. Tap a page name to close the side drawer and navigate to that page. Tap outside of the side drawer to close it without navigating to a different page.
Figures 21.11, 21.12, and 21.13 show the relevant parts of the two pages and the side drawer.
Configuration of the side drawer is specified in App.svelte
.
<script> import {onMount} from 'svelte'; import RadSideDrawerElement from 'svelte-native-nativescript-ui/sidedrawer'; import AboutPage from './AboutPage.svelte'; import HelloPage from './HelloPage.svelte'; import {goToPage, setDrawer} from './nav'; RadSideDrawerElement.register(); ❶ let drawer; onMount(() => setDrawer(drawer)); ❷ </script> <page> <radSideDrawer bind:this={drawer} drawerContentSize="200"> ❸ <radSideDrawer.drawerContent> ❹ <stackLayout> <label ❺ class="fas h2" text="" padding="10" horizontalAlignment="right" on:tap={() => drawer.closeDrawer()} /> <label text="About" on:tap={() => goToPage(AboutPage)} /> <label text="Hello" on:tap={() => goToPage(HelloPage)} /> </stackLayout> </radSideDrawer.drawerContent> <radSideDrawer.mainContent> <frame id="mainFrame" defaultPage={HelloPage} /> ❻ </radSideDrawer.mainContent> </radSideDrawer> </page> <style> label { color: white; font-size: 30; padding-bottom: 30; } stackLayout { background-color: cornflowerblue; padding: 20; } </style>
❶ This is required to use the radSideDrawer element.
❷ This lets nav.js know about the drawer.
❸ The default drawer width is wider than this app needs, so we set it to 200.
❹ This defines the content of the drawer.
❺ This renders an “X” that can be clicked to close the drawer.
❻ This is where each of the pages will be rendered.
Functions for navigating to a given page (optionally passing it props) and toggling the drawer between being open and closed are defined in nav.js
. The drawer is passed to setDrawer
in the preceding listing for App.svelte
.
import {navigate} from 'svelte-native'; let drawer; export function setDrawer(d) { ❶ drawer = d; } export function goToPage(page, props) { ❷ drawer.closeDrawer(); ❶ // Setting clearHistory to true prevents "<Back" button from appearing. navigate({page, props, clearHistory: true, frame: 'mainFrame'}); ❸ } export function toggleDrawer() { ❹ drawer.toggleDrawerState(); }
❶ This saves the drawer so it can be used in the goToPage and toggleDrawer functions.
❷ This is currently only called when a page name in the drawer is tapped. However, it could be used from any page to navigate to another page.
❸ “mainFrame” is the ID of the frame created in App.svelte.
❹ This is called when the hamburger icon in the Header component is tapped.
Each page renders a Header
component. It contains an actionBar
that renders a hamburger icon and a page title. Tapping the hamburger icon opens the side drawer.
<script> import {toggleDrawer} from './nav' import {isAndroid} from "tns-core-modules/platform" ❶ export let title = ''; </script> <actionBar title={title}> {#if isAndroid} ❷ <navigationButton icon="res://menu" on:tap={toggleDrawer} /> {:else} <actionItem icon="res://menu" ios.position="left" on:tap={toggleDrawer} /> {/if} </actionBar> <style> actionBar { background-color: cornflowerblue; color: white; } </style>
❶ This holds a Boolean value that indicates whether the current device is running Android.
❷ We want to display the hamburger menu in a platform-specific way.
The AboutPage
component used in listing 21.10 is shown in the following listing.
<script>
import Header from './Header.svelte';
import {singleLine} from './util'; ❶
let description = singleLine`
This is a Svelte Native app that demonstrates
using side drawer navigation.
`;
</script>
<page>
<Header title="About" />
<stackLayout class="p-20">
<label text={description} textWrap="true">
</label>
</stackLayout>
</page>
<style>
label {
color: red;
font-size: 32;
}
</style>
❶ This function name can appear at the beginning of a tagged template literal to turn a multiline, indented string into a single-line string with no indentation.
The HelloPage
component used in listing 21.10 is shown in the following listing.
<script>
import Header from './Header.svelte';
let name = 'World';
</script>
<page>
<Header title="Hello" />
<stackLayout class="p-20">
<flexboxLayout>
<label text="Name" />
<textField bind:text={name} /> ❶
</flexboxLayout>
<label class="greeting" text="Hello, {name}!" textWrap="true" />
</stackLayout>
</page>
<style>
.greeting {
color: blue;
font-size: 40;
}
label {
font-size: 20;
font-weight: bold;
}
textField {
flex-grow: 1;
font-size: 20;
}
</style>
❶ The user can enter a name here.
The singleLine
function used in listing 21.13 is shown in the following listing.
// This is a tagged template literal function that // replaces newline characters with a space // and then replaces consecutive spaces with one. export function singleLine(literals) { return literals .join(' ') .replace(/ /g, ' ') .replace(/ +/g, ' ') .trim(); }
That’s it! Copy the techniques from this code into any Svelte Native apps where you want to use drawer-based page navigation.
Svelte Native is a work in progress, and there are still some rough edges.
Some code errors cause the NativeScript Preview app to crash. This requires making code corrections, rerunning the tns
command, returning to the NativeScript Playground mobile app, and rescanning the QR code. Sometimes after making a valid code change, the app will crash again, and a long stack trace with no references to your code will be output. If there really are no errors in your code, repeating these steps often results in the app running correctly.
You have almost reached the end of your journey, but keep reading. There is plenty of good material in the appendixes!
Svelte Native builds on NativeScript to enable developing mobile applications using Svelte.
You can get started learning Svelte Native online using the Svelte Native REPL or the NativeScript Playground. Neither of these options requires installing any software.
For more serious Svelte Native development, you will want to create a project locally and use your preferred editor or IDE for development.
The NativeScript UI component library provides more advanced components than those provided by NativeScript.
3.133.12.172