Первое приложение

Цель

Процесс обучения идёт эффективнее, если учиться не на абстрактных вещах, а достигать в процессе обучения какой-то определенной цели. В этом разделе учебника мы создадим Todo-приложение с использованием Svelte Native. Оно будет иметь следующую функциональность:

  • Основной дизайн
    • Дизайн макета — две вкладки
    • Первая вкладка показывает активные задачи и позволяет добавить новые задачи
    • Вторая вкладка содержит список завершённых задач
  • Основная функциональность
    • Добавление задач: Пользователь может добавить задачу в виде текста
    • Просмотр задач: недавно добавленные задачи отображаются как активные и по ним можно тапнуть
    • Завершение задачи: при тапе по активной задаче открывается диалоговое окно с параметрами
    • Удаление задачи: при нажатии активной или завершённой задачи открывается диалоговое окно с параметрами
  • Дополнительный дизайн
    • Стилизация элементов input и button при добавлении задачи
    • Стилизация вкладок
    • Стилизация активных задач
    • Стилизация завершённых задач

TodoApp

Подготовка

Прежде чем начать, прочитайте Краткое руководство по началу работы и убедитесь, что можете запустить приложение на своем мобильном устройстве или эмуляторе.

Также предполагается, что вы уже знакомы с фреймворком Svelte. Изучите отличный учебник по Svelte, который быстро познакомит вас с основными возможностями этого фреймворка.

Основной дизайн

Давайте начнём с загрузки чистого шаблона приложения:

$ degit halfnelson/svelte-native-template todoapp
$ cd todoapp
$ npm install

Удалите имеющееся правило для селектора.btn изapp.css и в файле App.svelte напишите следующее:

App.svelte
<page class="page">
	<actionBar title="Мои Задачи" class="action-bar" />

	<tabView height="100%" androidTabsPosition="bottom">

	  <tabViewItem title="Активные" textWrap="true">
		<label>
			Эта панель для списка активных задач и добавления новых задач.
		</label>
	  </tabViewItem>

	  <tabViewItem title="Завершённые">
		<label text="Эта панель для списка завершённых задач" textWrap="true" />
	  </tabViewItem>

	</tabView>
</page>

ПРИМЕЧАНИЕ Обратите внимание, что все теги начинаются со строчной буквы. Это отличается от других реализаций NativeScript. Строчная буква позволяет компилятору Svelte понять, что это элементы NativeScript, а не компоненты Svelte. Думайте о <page> и <actionBar> как о дополнительном наборе тегов из ряда <ul>, <div> и т.д.

Что это означает?

Тег <page> — элемент пользовательского интерфейса верхнего уровня у любого приложения Svelte-Native. Все остальные UI элементы должны быть вложены в него.

Элемент <actionBar> показывает панель действий для <page>. В <page> не может содержаться более одного элемента <actionBar>.

Как правило, после <actionBar> нужно размещать компоненты навигации (боковое меню, вкладки) или компоненты макета. Эти элементы определяют макет приложения и позволяют определить, как размещать внутри другие элементы пользовательского интерфейса.

Имена классов в page иactionBar нужны для стилизации, по-умолчанию в таблице стилей используется основная тема NativeScript. Более подробную информацию об основной теме можно найти в Документации Nativescript.

Тег <label> мы использовали немного по-разному. В одном случае мы указали атрибут text=, а в другом — поместили текст между открывающим и закрывающим тегами. При компиляции текст между тегами будет автоматически присвоен атрибуту text.

Прогресс на данный момент

tab 1 tab 2

Основной дизайн: Добавление задач

У нас есть базовый дизайн, теперь дадим пользователю возможность добавления задач.

Замените содержимое первого <tabViewItem> на:

<stackLayout orientation="vertical" width="100%" height="100%">
	<gridLayout columns="2*,*" rows="*" width="100%" height="25%">
		<!-- Настраиваем текстовое поле и делаем, чтобы нажатие клавиши 'Ввод'
			  на клавиатуре давало тот же результат, что и нажатие кнопки. -->
		<textField col="0" row="0" bind:text="{textFieldValue}" hint="Что нужно сделать..." editable="true"
			on:returnPress="{onButtonTap}" />
		<button col="1" row="0" text="Добавить" on:tap="{onButtonTap}" />
	</gridLayout>

	<listView class="list-group" items="{todos}" on:itemTap="{onItemTap}" style="height:75%">
		<Template let:item>
			<label text="{item.name}" class="list-group-item-heading" textWrap="true" />
		</Template>
	</listView>
</stackLayout>

Теперь, в нижней части файла добавьте тег script:

<script>
	import { Template } from 'svelte-native/components'

	let todos = []
	let textFieldValue = ""

	function onItemTap(args) {
	  console.log('Нажат пункт с индексом: ' + args.index);
	}

	function onButtonTap() {
	  if (textFieldValue === "") return; //Запрещает пользователю вводить пустую строку.
	  console.log("Добавлена задача: " + textFieldValue + "."); // Отображает вновь добавленную задачу в консоли для отладки.
	  todos = [{ name: textFieldValue }, ...todos] // Добавляет задачи в массив todos. Задача сразу отобразится на экране.
	  textFieldValue = ""; // Очищает текстовое поле, чтобы пользователь мог сразу же добавить новые задачи.
	}
</script>

Что мы сделали?

Чтобы пользователь мог добавить пункт в список задач, нам нужно дать ему возможность ввести имя задачи. Для этого мы добавили текстовое поле <textField>. Кнопка <button> нужна ​​для отправки задачи, а <listView> для отображения списка задач.

Поскольку нам потребовалось добавить 3 элемента в <tabViewItem>, то мы воспользовались элементами макетов, чтобы объяснить NativeScript, где нужно разместить каждый элемент. Элементы в <stackLayout> помещаются в один ряд либо вертикально, либо горизонтально. Мы использовали его для размещения нашей формы ввода над <listView>. Элементы в <gridLayout> используется для размещения элементов в предопределенной сетке. Здесь он используется для размещения кнопки <button> справа и делает ее ширину равной половине ширины текстового поля <textInput>.

Элемент <ListView> содержит компонент Svelte <Template>, который используется для визуализации каждого элемента. Компонент Template необходимо импортировать также, как и любой компонент Svelte.

Когда вызывается обработчик onButtonTap, код, который мы добавили в блоке script, создаст новый массив todos, включающий добавленную задачу, и очистит текстовое поле. Обработчик onItemTap просто выводит в консоль индекс выбранного элемента при помощи console.log, который отлично работает в NativeScript.

ПРИМЕЧАНИЕ <listView> будет искать первый компонент <Template> в своих дочерних элементах. Компонент Template действует аналогично слотам в Svelte и отображает свое содержимое для каждого элемента, которые передаются директивой let:item в элементе <Template>.

Прогресс на данный момент

Мы можем добавлять пункты

Не очень красиво, но работает!

Основной дизайн: Завершение/Удаление задач

Никто не любит когда список задач лишь становится длиннее. Мы должны добавить возможность пометить задачу как выполненную или удалить её, если она была добавлена случайно:

В самом верху блока script добавьте этот импорт:

  import { action } from "tns-core-modules/ui/dialogs";

В верхней части блока script после let todos=[] добавьте еще одно объявление let dones=[], чтобы куда-то помещать завершенные задачи.

Затем замените нашу функцию onItemTap новой:

  function onItemTap(args) {
	action("Что нужно сделать с этой задачей?", "Отмена", [
	  "Завершить",
	  "Удалить"
	]).then(result => {
	  console.log(result); // Выводим выбраный пункт в консоль для отладки
	  let item = todos[args.index];
	  switch (result) {
		case "Завершить":
		  dones = [item, ...dones]; // Помещаем выбранную задачу наверх списка завершенных задач.
		  todos = todos.filter(t => t != item); // Удаляем выбранную задачу из активных.
		  break;
		case "Удалить":
		  todos = todos.filter(t => t != item); // Удаляем выбранную задачу.
		  break;
		case "Отмена" || undefined: // Просто закрываем диалог
		  break;
	  }
	});
  }

Что тут происходит?

NativeScript поставляется с модулем dialogs, который позволяет нам показывать небольшие модальные окна для получения данных от пользователя. Мы импортировали этот модуль, чтобы использовать метод action, который мы добавили в метод onItemTap. Когда пользователь выбирает 'Завершено', мы находим элемент с помощью args.index, который получаем из события, и удаляем элемент из массива todos, затем мы добавляем элемент в наш новый массив dones. Команда 'Удалить' просто удаляет элемент из todos.

ПРИМЕЧАНИЕ Обратите внимание, что мы переприсваиваем переменные dones иtodos во время операций удаления или добавления элементов. Реактивность Svelte работает на верхнем уровне и не может обнаруживать изменения внутри массива. Присваивая новое значение для dones и todos, мы гарантируем, что места в разметке, где используются эти массивы, будут обновлены вместе с массивами.

Прогресс на данный момент

Всплывающее окошко

Основной дизайн: Панель завершенных задач

Чтобы пользователь мог гордиться собой, хорошо бы иметь возможность показать ему список всех задач, которые он уже завершил. В этом разделе мы добавим элемент <listView>, чтобы отобразить список пунктов и сделаем возможность удалять их или восстанавливать обратно в незавершенные.

Сначала добавьте listView на вторую панель, заменив им label:

<listView class="list-group" items="{dones}" on:itemTap="{onDoneTap}">
	<Template let:item>
		<label text="{item.name}" class="list-group-item-heading" textWrap="true" />
	</Template>
</listView>

Затем добавьте код для onDoneTap в блок script:

function onDoneTap(args) {
  action("Что нужно сделать с этой задачей?", "Отмена", [
	"Вернуть",
	"Удалить"
  ]).then(result => {
	console.log(result); // Показываем в консоли выбранный вариант для отладки
	let item = dones[args.index]
	switch (result) {
	  case "Вернуть":
		todos = [item, ...todos]; // Помещаем выбранное задание в верхнюю часть невыполненных задач.
		dones = dones.filter(t => t != item); // Удаляем выбранное задание из списка выполненных.
		break;
	  case "Удалить":
		dones = dones.filter(t => t != item); // Удаляем выбранную задачу
		break;
	  case "Отмена" || undefined: // Просто закрываем диалог
		break;
	}
  });
}

Что мы только что сделали?

Чтобы отобразить наши завершённые задачи, мы добавили listView в элемент tabViewItem (панель 'Завершённые') и связали его с переменной dones, которую мы объявили на предыдущем шаге.

Мы добавили обработчик событий для обработки нажатий по 'завершённым' пунктам. Этот обработчик очень похож на обработчик, добавленный в предыдущем разделе, за исключением того, что он работает с массивом dones, а не с todos.

Дополнительный дизайн: стилизация поля и кнопки

С функционирование приложения мы разобрались, но не видать нам призов на конкурсе красоты. Чтобы порадовать наших пользователей, нам понадобится несколько минут, чтобы прописать некоторые стили. В этом разделе мы будем стилизовать текстовое поле и элементы кнопок.

В нижней части App.svelte добавьте блок <style> со следующим содержимым:

<style>
  button { 
	  font-size: 15; 
	  font-weight: bold; 
	  color: white; 
	  background-color: #2847D2; 
	  height: 40;
	  margin-top: 10; 
	  margin-bottom: 10; 
	  margin-right: 10; 
	  margin-left: 10; 
	  border-radius: 20px; 
  }

  textField {
	  font-size: 20;
	  color: #2847D2;
	  margin-top: 10;
	  margin-bottom: 10;
	  margin-right: 5;
	  margin-left: 20;
  }
</style>

Тэг style в нативном приложении!?

При разработке на NativeScript и Svelte, можно использовать глобальные CSS стили, изолированные CSS компонентов или инлайновые CSS для стилизации своего приложения. Глобальные CSS стили применяются первыми и находится в файле app.css в корне вашего проекта. В этом учебнике мы не будем заострять на нем внимание, подробнее можете почитать здесь: Стилизация.

Изолированные CSS стили применяются только к текущему компоненту и находятся в блоке <style> каждого компонента. В учебнике мы будем опираться в основном на изолированные и инлайновые CSS стили. Смотрите также: Изолированные стили.

С помощью селекторов типов можно выбрать UI компонент и применить к нему стилизацию. В нашем примере мы стилизовали элемент textField и button.

Прогресс на данный момент

стилизация кнопки

Дополнительный дизайн: Стилизация панелей

Приложение уже выглядит лучше, но нам надо что-то сделать с вкладками внизу.

Добавьте свойство selectedTabTextColor и tabTextFontSize в <TabView>:

  <tabView height="100%" androidTabsPosition="bottom" selectedTabTextColor="#2847D2" tabTextFontSize="15" >

Добавьте атрибут textTransform к каждой вкладке. Его можно использовать только на уровне <TabViewItem>.

  <tabViewItem title="Активные" textTransform="uppercase" >
  <tabViewItem title="Завершённые" textTransform="uppercase">

Это же не CSS!

Элемент <TabView> может принимать некоторые свойства стилей в качестве атрибутов. Можно применить текстовое преобразование к заголовку каждой вкладки (textTransform), изменить размер и цвет шрифта всех вкладок (tabTextFontSize, tabTextColor, selectedTabTextColor). Также можно изменить цвет фона вкладок tabBackgroundColor.

СОВЕТ Большинство CSS свойств в NativeScript имеют соответствующие атрибуты, которые могут быть указаны непосредственно на элементе.

Прогресс на данный момент

стилизация панелей

Дополнительный дизайн: Стилизация списков

Пункты списка слишком плотно прилегают друг к другу. Давайте добавим к ним отступы. Раскрасим активные задачи, а завершенные перечеркнем.

Отредактируйте оба элемента <listView> и добавьте атрибут separatorColor="transparent":

<listView class="list-group" items="{todos}" on:itemTap="{onItemTap}" style="height:75%" separatorColor="transparent">

и

<listView class="list-group" items="{dones}" on:itemTap="{onDoneTap}" separatorColor="transparent">

К тегу <label> внутри listView дляtodos добавьте todo-item active в атрибут класса:

 <label text="{item.name}" class="list-group-item-heading todo-item active" textWrap="true" />

Аналогично в <label> для завершённых добавьте todo-item completed:

 <label text="{item.name}" class="list-group-item-heading todo-item completed" textWrap="true" />

Добавьте эти CSS правила в блок style:


.todo-item {
  font-size: 20;
  margin-left: 20;
  padding-top: 5;
  padding-bottom: 10;
}

.todo-item.active {
  font-weight: bold;
  color: #2847D2;
}

.todo-item.completed {
  color: #d3d3d3;
  text-decoration: line-through;
}

Тут уже всё очевидно

В NativeScript вы не ограничены просто использованием имен элементов в качестве селекторов CSS. Мы добавили классы к <label> и применили правила CSS к этим классам. Мы также применили атрибут separatorColor непосредственно в listView, чтобы удалить разделитель между элементами, чтобы список выглядел лучше.

Наш законченный продукт

активные завершённые