1678810228
Разбивка на страницы — важная часть веб-разработки, которая позволяет пользователям легко перемещаться по большому списку данных. Он делит данные на небольшие фрагменты и отображает их в структурированном и управляемом виде.
В CakePHP 4 вам не нужно писать много кода для создания списка страниц. Для этого доступен метод paginate(), который выполняется из контроллера.
В этом руководстве я покажу, как создать разбиение на страницы в CakePHP 4 с данными базы данных MySQL.
В примере я использую usersтаблицы. Он имеет следующую структуру и данные:
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`username` varchar(50) NOT NULL,
`name` varchar(60) NOT NULL,
`gender` varchar(10) NOT NULL,
`email` varchar(60) NOT NULL,
`city` varchar(80) NOT NULL
);
ALTER TABLE `users`
ADD PRIMARY KEY (`id`);
ALTER TABLE `users`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
INSERT INTO `users` (`id`, `username`, `name`, `gender`, `email`, `city`) VALUES
(1, 'yssyogesh', 'Yogesh singh', 'male', 'yogesh@makitweb.com', 'Bhopal'),
(2, 'bsonarika', 'Sonarika Bhadoria', 'female', 'bsonarika@makitweb.com', 'Indore'),
(3, 'sunil', 'Sunil singh', 'male', 'sunil@makitweb.com', 'Pune'),
(4, 'vishal', 'Vishal Sahu', 'male', 'vishal@makitweb.com', 'Bhopal'),
(5, 'jiten', 'jitendra singh', 'male', 'jitendra@makitweb.com', 'Delhi'),
(6, 'shreya', 'Shreya joshi', 'female', 'shreya@makitweb.com', 'Indore'),
(7, 'abhilash', 'Abhilash namdev', 'male', 'abhilash@makitweb.com', 'Pune'),
(8, 'ekta', 'Ekta patidar', 'female', 'ekta@makitweb.com', 'Bhopal'),
(9, 'deepak', 'Deepak singh', 'male', 'deepak@makitweb.com', 'Delhi'),
(10, 'rohit', 'Rohit Kumar', 'male', 'rohit@makitweb.com', 'Bhopal'),
(11, 'bhavna', 'Bhavna Mahajan', 'female', 'bhavna@makitweb.com', 'Indore'),
(12, 'ajay', 'Ajay singh', 'male', 'ajay@makitweb.com', 'Delhi'),
(13, 'mohit', 'Mohit', 'male', 'mohit@makitweb.com', 'Pune'),
(14, 'akhilesh', 'Akhilesh Sahu', 'male', 'akhilesh@makitweb.com', 'Indore'),
(15, 'ganesh', 'Ganesh', 'male', 'ganesh@makitweb.com', 'Pune'),
(16, 'vijay', 'Vijay', 'male', 'vijay@makitweb.com', 'Delhi');
'Datasources' => [
'default' => [
'host' => '127.0.0.1',
/*
* CakePHP will use the default DB port based on the driver selected
* MySQL on MAMP uses port 8889, MAMP users will want to uncomment
* the following line and set the port accordingly
*/
//'port' => 'non_standard_port_number',
'username' => 'root',
'password' => 'root',
'database' => 'cakephp4',
/*
* If not using the default 'public' schema with the PostgreSQL driver
* set it here.
*/
//'schema' => 'myapp',
/*
* You can use a DSN string to set the entire configuration
*/
'url' => env('DATABASE_URL', null),
],
/*
* The test connection is used during the test suite.
*/
'test' => [
'host' => 'localhost',
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'test_myapp',
//'schema' => 'myapp',
'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tests.sqlite'),
],
],
bin/cake bake model Users
src/Model/Entity/User.php
src/Model/Table/UsersTable.php
Источник/Модель/Сущность/User.php
В этом классе Entity укажите имена полей, которые вы хотите разрешить вставлять и обновлять. Вы можете либо удалить имя поля, либо установить его, false если не хотите разрешать.
Завершенный код
<?php
declare(strict_types=1);
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Users extends Entity
{
protected $_accessible = [
'username' => true,
'name' => true,
'age' => true,
'gender' => true,
'email' => true,
'city' => true,
];
}
источник/Модель/Таблица/UsersTable.php
Этот класс Table сообщает ORM, какую таблицу необходимо использовать, и определяет правила проверки для полей.
Завершенный код
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UsersTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
}
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('username')
->maxLength('username', 50)
->requirePresence('username', 'create')
->notEmptyString('username');
$validator
->scalar('name')
->maxLength('name', 60)
->requirePresence('name', 'create')
->notEmptyString('name');
$validator
->integer('age')
->requirePresence('age', 'create')
->notEmptyString('age');
$validator
->scalar('gender')
->maxLength('gender', 10)
->requirePresence('gender', 'create')
->notEmptyString('gender');
$validator
->email('email')
->requirePresence('email', 'create')
->notEmptyString('email');
$validator
->scalar('city')
->maxLength('city', 80)
->requirePresence('city', 'create')
->notEmptyString('city');
return $validator;
}
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['username']), ['errorField' => 'username']);
return $rules;
}
}
Создать 1 метод –
Я разбиваю все usersзаписи таблицы. Если вы хотите разбить определенные записи на страницы, вы можете установить предложение where, используя $query– $query->where();.
В $settingsмассиве установите ограничение и порядок по умолчанию по имени столбца. Я устанавливаю ограничение в 5 и упорядочиваю по nameполям по ascпорядку.
Звонок $this->paginate()для создания пагинации. Вот $queryи проходите $settings.
Использование $this->set()перехода $usersк шаблону.
Завершенный код
<?php
declare(strict_types=1);
namespace App\Controller;
class UsersController extends AppController
{
public function index(){
$UsersTable = $this->getTableLocator()->get('Users');
// Paginate the ORM table.
$query = $UsersTable->find('all');
// Settings
$settings = [
'limit'=> '5',
'order' => ['Users.name' => 'asc']
];
$users = $this->paginate($query,$settings );
$this->set(compact('users'));
}
}
Создать Usersпапку в templates/месте. В Users папке создайте index.phpфайл – templates/Users/index.php.
Создать макет страницы —
Создать <table >.
Добавьте столбцы заголовков и с помощью Paginatorпомощника разрешите сортировку по имени столбца заголовка.
<?= $this->Paginator->sort('id', 'ID') ?>
Здесь первый параметр — это имя столбца сортировки, а второй параметр — заголовок столбца.
Например, я не установил сортировку в столбце электронной почты.
Цикл по, $usersесли он не пуст. Прочитайте значения и добавьте новые <tr> <td>.
Снова используйте Paginatorпомощник для добавления навигационных ссылок. Я добавляю списки «Предыдущий», «Следующий» и «Номер».
Завершенный код
<div class="row">
<div class="col-6">
<!-- User paginated list -->
<table >
<thead>
<tr>
<th><?= $this->Paginator->sort('id', 'ID') ?></th>
<th><?= $this->Paginator->sort('username', 'Username') ?></th>
<th><?= $this->Paginator->sort('id', 'Name') ?></th>
<th><?= $this->Paginator->sort('id', 'Gender') ?></th>
<th><?= $this->Paginator->sort('id', 'Age') ?></th>
<th>Email</th>
<th><?= $this->Paginator->sort('id', 'City') ?></th>
</tr>
</thead>
<tbody>
<?php if(count($users) > 0):?>
<?php foreach ($users as $user): ?>
<tr>
<td><?= $user['id'] ?></td>
<td><?= $user['username'] ?></td>
<td><?= $user['name'] ?></td>
<td><?= $user['gender'] ?></td>
<td><?= $user['age'] ?></td>
<td><?= $user['email'] ?></td>
<td><?= $user['city'] ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7">No record found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<!-- Pagination links -->
<ul class="pagination">
<?= $this->Paginator->prev("<<") ?>
<?= $this->Paginator->numbers() ?>
<?= $this->Paginator->next(">>") ?>
</ul>
</div>
</div>
Вызовите paginate()метод в контроллере, чтобы создать разбивку ваших данных на страницы. Отобразите его в шаблоне, зациклив сгенерированные данные и используя Paginatorпомощник.
Просмотрите этот официальный документ по созданию разбивки на страницы, если хотите узнать об этом больше.
Вы также можете ознакомиться с другими руководствами по CakePHP 4 на сайте.
Оригинальный источник статьи: https://makitweb.com/
1678806300
分页是 Web 开发的重要组成部分,它允许用户轻松浏览大量数据列表。它将数据分成小块,并以结构化和可管理的方式显示它们。
在 CakePHP 4 中,您不需要编写大量代码来创建分页列表。为此,可以使用从控制器执行的 paginate() 方法。
在本教程中,我展示了如何使用 MySQL 数据库数据在 CakePHP 4 中创建分页。
在示例中,我使用的是users表格。它具有以下结构和数据 -
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`username` varchar(50) NOT NULL,
`name` varchar(60) NOT NULL,
`gender` varchar(10) NOT NULL,
`email` varchar(60) NOT NULL,
`city` varchar(80) NOT NULL
);
ALTER TABLE `users`
ADD PRIMARY KEY (`id`);
ALTER TABLE `users`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
INSERT INTO `users` (`id`, `username`, `name`, `gender`, `email`, `city`) VALUES
(1, 'yssyogesh', 'Yogesh singh', 'male', 'yogesh@makitweb.com', 'Bhopal'),
(2, 'bsonarika', 'Sonarika Bhadoria', 'female', 'bsonarika@makitweb.com', 'Indore'),
(3, 'sunil', 'Sunil singh', 'male', 'sunil@makitweb.com', 'Pune'),
(4, 'vishal', 'Vishal Sahu', 'male', 'vishal@makitweb.com', 'Bhopal'),
(5, 'jiten', 'jitendra singh', 'male', 'jitendra@makitweb.com', 'Delhi'),
(6, 'shreya', 'Shreya joshi', 'female', 'shreya@makitweb.com', 'Indore'),
(7, 'abhilash', 'Abhilash namdev', 'male', 'abhilash@makitweb.com', 'Pune'),
(8, 'ekta', 'Ekta patidar', 'female', 'ekta@makitweb.com', 'Bhopal'),
(9, 'deepak', 'Deepak singh', 'male', 'deepak@makitweb.com', 'Delhi'),
(10, 'rohit', 'Rohit Kumar', 'male', 'rohit@makitweb.com', 'Bhopal'),
(11, 'bhavna', 'Bhavna Mahajan', 'female', 'bhavna@makitweb.com', 'Indore'),
(12, 'ajay', 'Ajay singh', 'male', 'ajay@makitweb.com', 'Delhi'),
(13, 'mohit', 'Mohit', 'male', 'mohit@makitweb.com', 'Pune'),
(14, 'akhilesh', 'Akhilesh Sahu', 'male', 'akhilesh@makitweb.com', 'Indore'),
(15, 'ganesh', 'Ganesh', 'male', 'ganesh@makitweb.com', 'Pune'),
(16, 'vijay', 'Vijay', 'male', 'vijay@makitweb.com', 'Delhi');
'Datasources' => [
'default' => [
'host' => '127.0.0.1',
/*
* CakePHP will use the default DB port based on the driver selected
* MySQL on MAMP uses port 8889, MAMP users will want to uncomment
* the following line and set the port accordingly
*/
//'port' => 'non_standard_port_number',
'username' => 'root',
'password' => 'root',
'database' => 'cakephp4',
/*
* If not using the default 'public' schema with the PostgreSQL driver
* set it here.
*/
//'schema' => 'myapp',
/*
* You can use a DSN string to set the entire configuration
*/
'url' => env('DATABASE_URL', null),
],
/*
* The test connection is used during the test suite.
*/
'test' => [
'host' => 'localhost',
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'test_myapp',
//'schema' => 'myapp',
'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tests.sqlite'),
],
],
bin/cake bake model Users
src/Model/Entity/User.php
src/Model/Table/UsersTable.php
源代码/模型/实体/User.php
在此实体类中指定要允许插入和更新的字段名称。false 如果您不想允许,您可以删除字段名称或将其设置为 。
完成代码
<?php
declare(strict_types=1);
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Users extends Entity
{
protected $_accessible = [
'username' => true,
'name' => true,
'age' => true,
'gender' => true,
'email' => true,
'city' => true,
];
}
src/模型/表/UsersTable.php
这个 Table 类告诉 ORM 需要使用哪个表并定义字段的验证规则。
完成代码
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UsersTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
}
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('username')
->maxLength('username', 50)
->requirePresence('username', 'create')
->notEmptyString('username');
$validator
->scalar('name')
->maxLength('name', 60)
->requirePresence('name', 'create')
->notEmptyString('name');
$validator
->integer('age')
->requirePresence('age', 'create')
->notEmptyString('age');
$validator
->scalar('gender')
->maxLength('gender', 10)
->requirePresence('gender', 'create')
->notEmptyString('gender');
$validator
->email('email')
->requirePresence('email', 'create')
->notEmptyString('email');
$validator
->scalar('city')
->maxLength('city', 80)
->requirePresence('city', 'create')
->notEmptyString('city');
return $validator;
}
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['username']), ['errorField' => 'username']);
return $rules;
}
}
创建 1 方法 –
我正在对所有users表记录进行分页。如果要对特定记录进行分页,则可以使用$query–设置 where 子句$query->where();。
在$settings数组中按列名设置限制和默认顺序。我将限制设置为 5 并按name字段asc排序。
调用$this->paginate()创建分页。在这里,通过$query和$settings。
使用$this->set()传递$users给模板。
完成代码
<?php
declare(strict_types=1);
namespace App\Controller;
class UsersController extends AppController
{
public function index(){
$UsersTable = $this->getTableLocator()->get('Users');
// Paginate the ORM table.
$query = $UsersTable->find('all');
// Settings
$settings = [
'limit'=> '5',
'order' => ['Users.name' => 'asc']
];
$users = $this->paginate($query,$settings );
$this->set(compact('users'));
}
}
Users在位置创建文件夹templates/。在Users 文件夹中创建index.php文件 – templates/Users/index.php.
创建分页布局 –
创建<table >。
添加标题列并使用Paginator帮助程序允许对标题列名称进行排序单击 –
<?= $this->Paginator->sort('id', 'ID') ?>
这里,第一个参数是排序列名,第二个参数是列标题。
例如,我没有在电子邮件列上设置排序。
$users如果它不为空,则循环。读取值并添加新的<tr> <td>.
再次使用Paginatorhelper 添加导航链接。我正在添加上一个、下一个和编号列表。
完成代码
<div class="row">
<div class="col-6">
<!-- User paginated list -->
<table >
<thead>
<tr>
<th><?= $this->Paginator->sort('id', 'ID') ?></th>
<th><?= $this->Paginator->sort('username', 'Username') ?></th>
<th><?= $this->Paginator->sort('id', 'Name') ?></th>
<th><?= $this->Paginator->sort('id', 'Gender') ?></th>
<th><?= $this->Paginator->sort('id', 'Age') ?></th>
<th>Email</th>
<th><?= $this->Paginator->sort('id', 'City') ?></th>
</tr>
</thead>
<tbody>
<?php if(count($users) > 0):?>
<?php foreach ($users as $user): ?>
<tr>
<td><?= $user['id'] ?></td>
<td><?= $user['username'] ?></td>
<td><?= $user['name'] ?></td>
<td><?= $user['gender'] ?></td>
<td><?= $user['age'] ?></td>
<td><?= $user['email'] ?></td>
<td><?= $user['city'] ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7">No record found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<!-- Pagination links -->
<ul class="pagination">
<?= $this->Paginator->prev("<<") ?>
<?= $this->Paginator->numbers() ?>
<?= $this->Paginator->next(">>") ?>
</ul>
</div>
</div>
调用paginate()控制器中的方法来创建数据的分页。通过循环生成的数据并使用Paginator帮助程序将其显示在模板中。
如果您想了解更多信息,请查看此有关分页创建的官方文档。
您还可以在站点上查看更多CakePHP 4 教程。
文章原文出处:https: //makitweb.com/
1678802400
Pagination is an essential part of web development that allows users to navigate through a large list of data easily. It divides the data into small chunks and displays them in a structured and manageable way.
In CakePHP 4 you don’t need to write lots of code to create a pagination list. For this paginate() method is available that executes from the controller.
In this tutorial, I show how you can create a pagination in CakePHP 4 with MySQL database data.
In the example, I am using users
Tables. It has the following structure and data –
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`username` varchar(50) NOT NULL,
`name` varchar(60) NOT NULL,
`gender` varchar(10) NOT NULL,
`email` varchar(60) NOT NULL,
`city` varchar(80) NOT NULL
);
ALTER TABLE `users`
ADD PRIMARY KEY (`id`);
ALTER TABLE `users`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
INSERT INTO `users` (`id`, `username`, `name`, `gender`, `email`, `city`) VALUES
(1, 'yssyogesh', 'Yogesh singh', 'male', 'yogesh@makitweb.com', 'Bhopal'),
(2, 'bsonarika', 'Sonarika Bhadoria', 'female', 'bsonarika@makitweb.com', 'Indore'),
(3, 'sunil', 'Sunil singh', 'male', 'sunil@makitweb.com', 'Pune'),
(4, 'vishal', 'Vishal Sahu', 'male', 'vishal@makitweb.com', 'Bhopal'),
(5, 'jiten', 'jitendra singh', 'male', 'jitendra@makitweb.com', 'Delhi'),
(6, 'shreya', 'Shreya joshi', 'female', 'shreya@makitweb.com', 'Indore'),
(7, 'abhilash', 'Abhilash namdev', 'male', 'abhilash@makitweb.com', 'Pune'),
(8, 'ekta', 'Ekta patidar', 'female', 'ekta@makitweb.com', 'Bhopal'),
(9, 'deepak', 'Deepak singh', 'male', 'deepak@makitweb.com', 'Delhi'),
(10, 'rohit', 'Rohit Kumar', 'male', 'rohit@makitweb.com', 'Bhopal'),
(11, 'bhavna', 'Bhavna Mahajan', 'female', 'bhavna@makitweb.com', 'Indore'),
(12, 'ajay', 'Ajay singh', 'male', 'ajay@makitweb.com', 'Delhi'),
(13, 'mohit', 'Mohit', 'male', 'mohit@makitweb.com', 'Pune'),
(14, 'akhilesh', 'Akhilesh Sahu', 'male', 'akhilesh@makitweb.com', 'Indore'),
(15, 'ganesh', 'Ganesh', 'male', 'ganesh@makitweb.com', 'Pune'),
(16, 'vijay', 'Vijay', 'male', 'vijay@makitweb.com', 'Delhi');
config/app_local.php
file.Datasources
default
.'Datasources' => [
'default' => [
'host' => '127.0.0.1',
/*
* CakePHP will use the default DB port based on the driver selected
* MySQL on MAMP uses port 8889, MAMP users will want to uncomment
* the following line and set the port accordingly
*/
//'port' => 'non_standard_port_number',
'username' => 'root',
'password' => 'root',
'database' => 'cakephp4',
/*
* If not using the default 'public' schema with the PostgreSQL driver
* set it here.
*/
//'schema' => 'myapp',
/*
* You can use a DSN string to set the entire configuration
*/
'url' => env('DATABASE_URL', null),
],
/*
* The test connection is used during the test suite.
*/
'test' => [
'host' => 'localhost',
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'test_myapp',
//'schema' => 'myapp',
'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tests.sqlite'),
],
],
Users
Model.bin/cake bake model Users
src/Model/Entity/User.php
src/Model/Table/UsersTable.php
src/Model/Entity/User.php
In this Entity class specify field names that you want to allow insertion and updation. You can either remove the field name or set it to false
if you don’t want to allow.
Completed Code
<?php
declare(strict_types=1);
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Users extends Entity
{
protected $_accessible = [
'username' => true,
'name' => true,
'age' => true,
'gender' => true,
'email' => true,
'city' => true,
];
}
src/Model/Table/UsersTable.php
This Table class tells ORM which table needs to use and defines validation rules for the fields.
Completed Code
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UsersTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
}
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('username')
->maxLength('username', 50)
->requirePresence('username', 'create')
->notEmptyString('username');
$validator
->scalar('name')
->maxLength('name', 60)
->requirePresence('name', 'create')
->notEmptyString('name');
$validator
->integer('age')
->requirePresence('age', 'create')
->notEmptyString('age');
$validator
->scalar('gender')
->maxLength('gender', 10)
->requirePresence('gender', 'create')
->notEmptyString('gender');
$validator
->email('email')
->requirePresence('email', 'create')
->notEmptyString('email');
$validator
->scalar('city')
->maxLength('city', 80)
->requirePresence('city', 'create')
->notEmptyString('city');
return $validator;
}
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['username']), ['errorField' => 'username']);
return $rules;
}
}
UsersController.php
file in src/Controller/
folder.UsersController
Class that extends AppController
.Create 1 method –
Users
Table using – $this->getTableLocator()->get('Users')
.I am paginating all users
table records. If you want to paginate specific records then you can set where clause using $query
– $query->where();
.
In the $settings
Array set limit and default order by column name. I set a limit of 5 and order by name
field in asc
order.
Call $this->paginate()
to create pagination. Here, pass $query
and $settings
.
Using $this->set()
pass $users
to the template.
Completed Code
<?php
declare(strict_types=1);
namespace App\Controller;
class UsersController extends AppController
{
public function index(){
$UsersTable = $this->getTableLocator()->get('Users');
// Paginate the ORM table.
$query = $UsersTable->find('all');
// Settings
$settings = [
'limit'=> '5',
'order' => ['Users.name' => 'asc']
];
$users = $this->paginate($query,$settings );
$this->set(compact('users'));
}
}
Create Users
folder in templates/
location. In the Users
folder create index.php
file – templates/Users/index.php
.
Create Pagination layout –
Create <table >
.
Add header columns and with the use of Paginator
helper allow sorting on the header column name click –
<?= $this->Paginator->sort('id', 'ID') ?>
Here, the first parameter is the sorting column name and 2nd parameter is the Column title.
For example purpose, I have not set sorting on the Email column.
Loop on the $users
if it is not empty. Read values and add new <tr> <td>
.
Again use Paginator
helper for adding navigation links. I am adding the Previous, next, and number lists.
Completed Code
<div class="row">
<div class="col-6">
<!-- User paginated list -->
<table >
<thead>
<tr>
<th><?= $this->Paginator->sort('id', 'ID') ?></th>
<th><?= $this->Paginator->sort('username', 'Username') ?></th>
<th><?= $this->Paginator->sort('id', 'Name') ?></th>
<th><?= $this->Paginator->sort('id', 'Gender') ?></th>
<th><?= $this->Paginator->sort('id', 'Age') ?></th>
<th>Email</th>
<th><?= $this->Paginator->sort('id', 'City') ?></th>
</tr>
</thead>
<tbody>
<?php if(count($users) > 0):?>
<?php foreach ($users as $user): ?>
<tr>
<td><?= $user['id'] ?></td>
<td><?= $user['username'] ?></td>
<td><?= $user['name'] ?></td>
<td><?= $user['gender'] ?></td>
<td><?= $user['age'] ?></td>
<td><?= $user['email'] ?></td>
<td><?= $user['city'] ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7">No record found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<!-- Pagination links -->
<ul class="pagination">
<?= $this->Paginator->prev("<<") ?>
<?= $this->Paginator->numbers() ?>
<?= $this->Paginator->next(">>") ?>
</ul>
</div>
</div>
Call the paginate()
method in the controller to create the pagination of your data. Display it in the template by looping on the generated data and using the Paginator
helper.
View this official document for pagination creation if you want to learn more about it.
You can also check out more CakePHP 4 tutorials on the site.
Original article source at: https://makitweb.com/
1677495540
In this article, we will see how to create a crud operation in laravel 10. Here, we will learn about laravel 10 crud operation step by step. Day to day laravel improves secure, modern, and scalable applications. Also, it creates powerful and reusable codes and functionalities in the laravel framework.
So, here we will see how to create a CRUD operation in laravel 10, In this example, we have performed create, read, update, and delete.
So, let's see laravel 10 crud operation step by step, how to create crud operation in laravel 10, crud operations in laravel 10, laravel 10 crud operation, and laravel 10 crud step by step.
Step 1: Install Laravel 10 Application
Here, we will create a new project using the below command for CRUD operation in Laravel 10.
composer create-project --prefer-dist laravel/laravel blog
Step 2: Database Configuration
Now, we will set up database configuration like database name, username, password, etc for our crud application of laravel 10. So, copy the below code in your .env file and paste the details below.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_10_crud
DB_USERNAME=root
DB_PASSWORD=root
Step 3: Create a Migration
Here, we will create migration for the "posts" table using Laravel 10 php artisan command.
php artisan make:migration create_posts_table --create=posts
Migration:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePostsTable extends Migration {
/**
* Run the migrations.
*
* @return void */
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name')->nullable();
$table->longText('detail')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
}
Now run migration using the below command.
php artisan migrate
Step 4: Add Resource Route
Now, Add the Resource route in the web.php file.
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group that
| contains the "web" middleware group. Now create something great!
|
*/
Route::resource('posts', PostController::class);
Step 5: Add Controller and Model
In this step, we will create the PostController using the following command. Using the below command, you can create an index(), create(), store(), edit(), update() and destroy().
php artisan make:controller PostController --resource --model=Post
Now make changes in the Post.php Model.
App/Models/Post.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'id', 'name', 'detail'
];
}
After changes in the model, we need to add code in PostController.php
app/Http/Controllers/PostController.php
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$posts = Post::latest()->paginate(10);
return view('post.index',compact('posts'))->with('i', (request()->input('page', 1) - 1) * 10);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('post.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'detail' => 'required',
]);
Post::create($request->all());
return redirect()->route('posts.index')->with('success','Post created successfully.');
}
/**
* Display the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function show(Post $post)
{
return view('post.show',compact('post'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function edit(Post $post)
{
return view('post.edit',compact('post'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Post $post)
{
$request->validate([
'name' => 'required',
'detail' => 'required',
]);
$post->update($request->all());
return redirect()->route('posts.index')->with('success','Post updated successfully');
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')->with('success','Post deleted successfully');
}
}
Step 6: Add Blade Files
Now, create blade files for layout and post file. So, create new folder "posts" and then create blade files of the CRUD example in laravel 10.
1. layout.blade.php
2. index.blade.php
3. create.blade.php
4. edit.blade.php
5. show.blade.php
resources/views/posts/layout.blade.php
<!DOCTYPE html>
<html>
<head>
<title>Laravel 10 CRUD Operation Step By Step - Websolutionstuff</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
@yield('content')
</div>
</body>
</html>
resources/views/posts/index.blade.php
@extends('post.layout')
@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>Laravel 10 CRUD Operation Step By Step - Websolutionstuff</h2>
</div>
<div class="pull-right">
<a class="btn btn-success" href="{{ route('posts.create') }}"> Create New Post</a>
</div>
</div>
</div>
@if ($message = Session::get('success'))
<div class="alert alert-success">
<p>{{ $message }}</p>
</div>
@endif
<table class="table table-bordered">
<tr>
<th>No</th>
<th>Name</th>
<th>Details</th>
<th width="280px">Action</th>
</tr>
@foreach ($posts as $post)
<tr>
<td>{{ ++$i }}</td>
<td>{{ $post->name }}</td>
<td>{{ $post->detail }}</td>
<td>
<form action="{{ route('posts.destroy',$post->id) }}" method="POST">
<a class="btn btn-info" href="{{ route('posts.show',$post->id) }}">Show</a>
<a class="btn btn-primary" href="{{ route('posts.edit',$post->id) }}">Edit</a>
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
@endforeach
</table>
{!! $posts->links() !!}
@endsection
resources/views/posts/create.blade.php
@extends('post.layout')
@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>Add New Post</h2>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="{{ route('posts.index') }}"> Back</a>
</div>
</div>
</div>
@if ($errors->any())
<div class="alert alert-danger">
<strong>Error!</strong> <br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('posts.store') }}" method="POST">
@csrf
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" class="form-control" placeholder="Name">
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Detail:</strong>
<textarea class="form-control" style="height:150px" name="detail" placeholder="Detail"></textarea>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
@endsection
resources/views/posts/edit.blade.php
@extends('post.layout')
@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>Edit Post</h2>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="{{ route('posts.index') }}"> Back</a>
</div>
</div>
</div>
@if ($errors->any())
<div class="alert alert-danger">
<strong>Error!</strong> <br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('posts.update',$post->id) }}" method="POST">
@csrf
@method('PUT')
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" value="{{ $post->name }}" class="form-control" placeholder="Name">
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Detail:</strong>
<textarea class="form-control" style="height:150px" name="detail" placeholder="Detail">{{ $post->detail }}</textarea>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
@endsection
resources/views/posts/show.blade.php
@extends('post.layout')
@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2> Show Post</h2>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="{{ route('posts.index') }}"> Back</a>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Name:</strong>
{{ $post->name }}
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Details:</strong>
{{ $post->detail }}
</div>
</div>
</div>
@endsection
Now, we will laravel 10 crud operation application using the following command.
php artisan serve
Original article source at: https://websolutionstuff.com/
1676648700
When you think of running Android on your Ubuntu, you must utilize an emulator to make it possible. When using Windows, you can run Android using a tool like Bluestacks. Linux has different Android emulators you can use to install and run your Android apps.
Linux does not support Android applications unless you install an app like Anbox, which allows you to emulate Android on Ubuntu 22.04. This post details all the steps to follow.
Android is the most popularly used operating system for mobile. When you want to experience the same feeling on your Linux system, your best chance is to use an emulator. With an Android emulator, you can install and run Android applications without affecting your host system. For this guide, we will focus on the Anbox Android emulator.
Anbox is an open-source compatibility Linux system emulation tool that allows users to install and run Android applications. Anbox utilizes Linux Containers (LXC) to run the Android runtime environment and recreates the Android directory structure. With Anbox, all the Android applications installed use the native Linux kernel to run.
For Ubuntu 22.04, Anbox is available as a snap package. However, we must use the —devmode option as Anbox is yet to be fully available as a snap package.
Run the below command to install the Anbox snap.
$ sudo snap install --devmode --beta anbox
Let snap download and start the installation.
Once the command executes and Anbox installs, you will get an output like the one below confirming the installation was successful.
Anbox is now available in your Ubuntu 22.04 and you can open it from your applications.
Once you click on Anbox, it will start loading to start the Android runtime environment on your Linux system.
Once Anbox starts, it will open its Anbox application manager dashboard containing the different applications available for any Android system.
You can click on any option from the dashboard to explore more. For instance, if you click the Email option, you will be prompted to set up your account before using Email.
Still, you can click on an application like Calendar, which will open the same calendar application you get when using your Android mobile.
If you click on the Settings application, you can get all the details about the emulated Android. You will get the Settings page open like in the output below.
You can click on any option to view more details. For instance, we have opened the Languages & Input option to change the system’s language or the keyboard used for the emulated Android.
Suppose you want to view the available security features or set up your login authentication, clicking on security opens like an actual Android phone.
You can also access the file manager to view the different available options, such as the videos, if you have any.
Lastly, you can click on the Apps option to view the available applications on the emulated Android. You will notice that it has the same applications as most Android phones.
When you want to remove Anbox from your Ubuntu 22.04, run the command below.
$ sudo snap remove anbox
Running Android on Ubuntu requires you to use an emulator to help run the Android runtime environment on your Linux system. This guide has focused on how to use the Anbox Android emulator tool to emulate Android on Ubuntu 22.04. We have seen how to install, use, and remove Anbox.
Original article source at: https://linuxhint.com/
1676485360
In this tutorial, you'll learn how to create a blog from scratch using HTML, CSS, and JavaScript.
We'll start by setting up the basic structure of the blog, then add styling with CSS, and finally,
include some interactive features using JavaScript.
By the end of this video, you'll have a fully functional blog that you can customize and use as a personal website, portfolio,
or anything else you like.
🔗 Essential links
-----------------------------
Source Code: https://upfiles.com/AF1j
📺 My Channel :
https://www.youtube.com/channel/UCHI9Mo7HCSlqum1UMP2APFQ
🔔 Subscribe :
https://www.youtube.com/channel/UCHI9Mo7HCSlqum1UMP2APFQ
🕐 New Videos Every Week
🔗 Essential links
-----------------------------
Image : https://unsplash.com/
Font : https://fonts.google.com/
FontAwesome : https://fontawesome.com/
📹 Others Videos
-----------------------------
Music Landing Page Website Design using Html CSS & Javascript:
➤ Watch: https://youtu.be/HoTbTNju0eg
Sidebar Menu in HTML CSS & JavaScript | Dark/Light Mode :
➤ Watch: https://youtu.be/QQni50OBNpU
Responsive Bloggar - News Magazine :
➤ Watch: https://youtu.be/Js-J_SVZEnY
Responsive Minimal Portfolio Website :
➤ Watch: https://youtu.be/e1vjlzg8i3Y
💜 Like - Follow & Subscribe Me
-----------------------------
Twitter : https://bit.ly/3IOBEqc
Instagram : https://bit.ly/3GHoQyT
Facebook : https://bit.ly/3IMfk04
Linkedin : https://bit.ly/3INnwNY
Tags:
#HTML #CSS #JavaScript #webmonster #responsivewebsite #LandinPage #web_development #blogging #create a blog #how_to_build_a_blog #website_design #website_development #front-end_development #coding_tutorial #step-by-step_tutorial #beginner-friendly
Link The Video:
➤ Watch: https://youtu.be/a0YjoJhIjXQ
1676334060
Follow this blueprint for planning, hosting, and managing a successful hackathon.
Hackathons are easy. How much thought do you need to put into them anyway? Just set a date, and people will show up. Well, that is not quite true!
While you may get lucky with that approach, the reality is that hackathons are a keystone experience in the tech industry, and attendees have specific expectations. Not only that, but your organization also has certain needs and should set goals for a hackathon. So, how do you ensure that a hackathon works for your organization and attendees?
A successful hackathon depends on several decisions that tend to be recursive. Decisions about what you want to achieve will impact what resources you allot and how you want to communicate. Those decisions affect whether you go virtual or in person, and that decision will once again impact the resources you need and how you communicate. Alignment when planning hackathons is not just about getting people to agree. You will have a whole suite of decisions that must internally align. For example, a technically difficult hackathon might not be able to attract a large audience (ask me how I know!) and will require a specialized recruitment strategy that requires different resources.
I've done many hackathons over the years, including just a few months back, when my organization hosted a hackathon that led to new features that we will incorporate into the next version of our open source product, Traefik Proxy 3.0. So, trust me when I say planning a hackathon that will enrich attendees and create valuable outcomes for your project is about more than hope, pizza, and chaos.
This article uses the most recent Traefik Labs Hackathon as a blueprint. I share a checklist, tips, and tricks to help you identify your objectives, plan, manage the contest and compensation, share your results, and manage the long tail of the hackathon (the work isn't over when the hackathon ends!)
This guide serves as a model for you to outline best practices so that you, too, can hold a successful hackathon with a sizable target audience that delivers results!
The first and most crucial step is to set your goals. But this is no simple affair. Before you set goals, you need to coordinate internally on multiple fronts and ask questions such as:
Hackathons are cross-functional. No hackathon is run by a community person alone. It is important to ensure everyone is aligned on the goals, what is required to achieve them, and that the necessary resources are committed. This probably sounds super corporate, but these functions exist even within the smallest projects. A project needs adoption and code. It also needs value decisions based on who will be using it. And, of course, projects need passionate contributors.
Hackathons require cross-functional resources. One team with a single set of resources cannot successfully run a hackathon. The organization must make various resources available, including:
For these reasons, hackathons usually support cross-functional goals. Your Community Team, for example, might want to build ownership and convert users to active community members. The Marketing Team might want to enhance awareness and court new users. The Engineering Team might need new perspectives on specific needs or challenges. The Product Team might have goals or no-go areas the community should be aware of.
And last but not least, the hackathon budget is cross-functional. I am sorry to inform you, but hackathons ain't free! Your largest expense is always the dedicated time of your team.
Setting your goals is the most important part of a successful hackathon. If you don't know what you want to do or why a hackathon is important, at best, it will have a ton of wasted potential and be a disconnected mess at worst.
Communities feed off of ownership. Decide what you need from your community and what ownership stake you want community members to have. Without a clear understanding of this, your hackathon might not reach its full potential in empowering your community.
Be very careful with your hackathon design and goals. Different types of hackathons appeal to different skill levels. If the code you're looking for is very advanced, take the extra time to court the right audience and accept that it will include less overall attendance. Cast a wide net if the contributions can vary in skill and experience.
Note: This is more easily achievable with smaller groups who know each other and have a shared experience with the project. You also need to carefully evaluate the skills required to build your project.
Hackathons are a great place to begin if you are just starting and want to build awareness around your product/brand. However, there are a few conditions.
I can think of no better way to connect new users to your project than a hackathon. Not only will your users become intimately familiar with your project, but hackathons also have a unique way of engendering a sense of ownership, rarely achievable through other types of events.
Assuming you have pinpointed why you want to host a hackathon and what you want to achieve, it's time to assess the characteristics that a participant needs to be successful. Use your decisions about your goals to identify your audience to ask what type of community member can help you achieve your objectives. Use the list of comparisons below:
Your most active community members must look a bit like your target audience.
You might rethink your goals if your target audience doesn't align with at least 80% of the people you know you can attract. Accurately identifying your target audience will go a long way to making your communication strategy around the hackathon and the hackathon itself more successful.
Perfect, now that you answered the first two big questions and have your goals laid down, it's time for the third big question—how will you measure those goals? Inspiring your internal teams and your community to work together in building the future of your project, engendering ownership, and increasing engagement are awesome, but you can't determine success if you can't measure your goals.
Defining benchmarks for long-term success is just as important. Here are a few examples of what could indicate long-term success:
I know what you're thinking—is in-person even a consideration? We've all gotten so used to doing everything virtually in the post-covid world. So, are the days of in-person gone? I would argue no, they are not. With care and safety measures in place, in-person events are the heart and soul of hackathons.
And while many pros come with in-person hackathons, it doesn't mean that the virtual experience only comes with cons. Granted, nothing replaces that feeling of late nights with pizza, off-the-cuff remarks that end up changing your entire project, and a friendly set of eyes over your shoulder as you test or debug. But...
Did you decide to do a virtual hackathon? Great! Now, you need to determine the type of virtual hackathon you want. Do you envision a prolonged or intensive timeline? Keep in mind that the type of virtual hackathon you choose will determine, to some extent, your target audience and communication strategy.
Extended timeline:
Intense timeline:
Speaking of communication, once you have your goals, you must decide who communicates with participants and how. It's common to choose between the popular apps of the day. Your choice impacts the event's feel. Different chat applications and collaboration platforms have their own cultures and strengths. The decision you made early on about how to host your hackathon (in-person or virtual, prolonged or intense timeline) is likely to have the most significant impact on your communication strategy.
If you are running an in-person hackathon, consider it a genuine event—it feels almost like a conference. In-person hackathons often include the following:
Each of the above has different communication needs:
Making this all happen requires the resources and specialized knowledge from your Community, Product Managers, and teach-savvy teams. From past experience, it took a team of community members and staff to manage an event of this scope. To be successful, your team will need specialized people as well.
You also need to decide what types of communication you want to foster and who is responsible for it:
For virtual hackathons, the choice of a communication platform depends heavily on the outcome you want to achieve, the timeline you've chosen for your hackathon (prolonged or intensive), and the type of communication you wish to facilitate (synchronous or asynchronous).
Using Pull Requests and Issues on Git hosts (asynchronous):
Using a chat application (synchronous):
Regardless of which platform you choose, you need a communication plan that identifies when every person on your team is available. Managing a virtual hackathon can get quite tricky, primarily due to the different timezones—people can participate whenever they want, from wherever they want. You must plan to accommodate participants across all time zones and for every occasion. Draw up a plan with who is responsible (and when) for the following:
Is your hackathon a contest? Hackathon participants are often content with grand prizes and "swagpaloozas" for top contributions. But before you decide on the fun stuff (the actual awards), you must determine what your contest values.
On the other hand, some may argue that competition is overrated. If your goal is participation, feel free to reward every single one of your participants for simply giving back! Hacktoberfest is a great example of this approach.
Everyone loves swag! And your participants would certainly appreciate a token to remember this event, whether virtual or in person. Swag has two purposes:
The community loves swag, but they don't love boring swag! You probably distributed your existing t-shirts and stickers during another event. Make your hackathon memorable and go for new, exciting, and exclusive designs. Shirts are great, and hoodies reign supreme. But think about cool swag participants may not have already. Think of something that could become their new staple item, like backup batteries or hats (both popular at HackerOne). Personally, my own home features some towels and slippers from hackathons!
Setting your goals and deciding on amazing grand prizes and swag are all important steps. But how will anyone know your hackathon is happening if you don't get the word out? You need to investigate the available channels carefully, and you need to be bold with your promotion. I'm talking blogs, vlogs, emails, social media—anything you can get your hands on.
However, depending on your defined goals, you need to invest in the appropriate channel. Where you advertise depends on who you want to invite to your hackathon.
Yay, the hackathon is over! Now all hackathon-related activities can stop, and we no longer need to pull resources, right? Wrong! Think of hackathons as only one step of the road in a series of events in your software development and community building. To deem your hackathon a success, you must be prepared to engage in post-event activities.
If you started reading this article thinking that hackathons aren't that hard to pull off, I'm sorry to have burst your bubble! And although I sincerely believe hackathons are a great way to engage and communicate with your community on so many levels, having just the intention does not guarantee the results.
For a hackathon to be successful, you need to be meticulous and prepared to invest significant resources and effort to plan and execute it properly.
Thank you for reading, and I hope this checklist helps you successfully organize your next hackathon!
Download this article as a PDF and EPUB
Original article source at: https://opensource.com/
1674813120
In this article, we will see how to validate multi step form wizard using jquery. Here, we will learn to validate the multi step form using jquery. First, we create the multi step form using bootstrap. Also, in this example, we are not using any jquery plugin for multi step form wizard.
So, let's see jquery multi step form with validation, how to create multi step form, multi step form wizard with jquery validation, bootstrap 4 multi step form wizard with validation, multi step form bootstrap 5, and jQuery multi step form with validation and next previous navigation.
Add HTML:
<html lang="en">
</head>
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap" rel="stylesheet">
</head>
<body>
<div class="main">
<h3>How To Validate Multi Step Form Using jQuery - Websolutionstuff</h3>
<form id="multistep_form">
<!-- progressbar -->
<ul id="progress_header">
<li class="active"></li>
<li></li>
<li></li>
</ul>
<!-- Step 01 -->
<div class="multistep-box">
<div class="title-box">
<h2>Create your account</h2>
</div>
<p>
<input type="text" name="email" placeholder="Email" id="email">
<span id="error-email"></span>
</p>
<p>
<input type="password" name="pass" placeholder="Password" id="pass">
<span id="error-pass"></span>
</p>
<p>
<input type="password" name="cpass" placeholder="Confirm Password" id="cpass">
<span id="error-cpass"></span>
</p>
<p class="nxt-prev-button"><input type="button" name="next" class="fs_next_btn action-button" value="Next" /></p>
</div>
<!-- Step 02 -->
<div class="multistep-box">
<div class="title-box">
<h2>Social Profiles</h2>
</div>
<p>
<input type="text" name="twitter" placeholder="Twitter" id="twitter">
<span id="error-twitter"></span>
</p>
<p>
<input type="text" name="facebook" placeholder="Facebook" id="facebook">
<span id="error-facebook"></span>
</p>
<p>
<input type="text" name="linkedin" placeholder="Linkedin" id="linkedin">
<span id="error-linkedin"></span>
</p>
<p class="nxt-prev-button">
<input type="button" name="previous" class="previous action-button" value="Previous" />
<input type="button" name="next" class="ss_next_btn action-button" value="Next" />
</p>
</div>
<!-- Step 03 -->
<div class="multistep-box">
<div class="title-box">
<h2>Personal Details</h2>
</div>
<p>
<input type="text" name="fname" placeholder="First Name" id="fname">
<span id="error-fname"></span>
</p>
<p>
<input type="text" name="lname" placeholder="Last Name" id="lname">
<span id="error-lname"></span>
</p>
<p>
<input type="text" name="phone" placeholder="Phone" id="phone">
<span id="error-phone"></span>
</p>
<p>
<textarea name="address" placeholder="Address" id="address"></textarea>
<span id="error-address"></span>
</p>
<p class="nxt-prev-button"><input type="button" name="previous" class="previous action-button" value="Previous" />
<input type="submit" name="submit" class="submit_btn ts_next_btn action-button" value="Submit" />
</p>
</div>
</form>
<h1>You are successfully logged in</h1>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.0/jquery.easing.js" type="text/javascript"></script>
</body>
</html>
Add CSS:
body {
display: inline-block;
width: 100%;
height: 100vh;
overflow: hidden;
background-image: url("https://img1.akspic.com/image/80377-gadget-numeric_keypad-input_device-electronic_device-space_bar-3840x2160.jpg");
background-repeat: no-repeat;
background-size: cover;
position: relative;
margin: 0;
font-weight: 400;
font-family: 'Roboto', sans-serif;
}
body:before {
content: "";
display: block;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.main {
position: absolute;
left: 0;
right: 0;
top: 30px;
margin: 0 auto;
height: 515px;
}
input:-internal-autofill-selected {
background-color: #fff !important;
}
#multistep_form {
width: 550px;
margin: 0 auto;
text-align: center;
position: relative;
height: 100%;
z-index: 999;
opacity: 1;
visibility: visible;
}
/*progress header*/
#progress_header {
overflow: hidden;
margin: 0 auto 30px;
padding: 0;
}
#progress_header li {
list-style-type: none;
width: 33.33%;
float: left;
position: relative;
font-size: 16px;
font-weight: bold;
font-family: monospace;
color: #fff;
text-transform: uppercase;
}
#progress_header li:after {
width: 35px;
line-height: 35px;
display: block;
font-size: 22px;
color: #888;
font-family: monospace;
background-color: #fff;
border-radius: 100px;
margin: 0 auto;
background-repeat: no-repeat;
font-family: 'Roboto', sans-serif;
}
#progress_header li:nth-child(1):after {
content: "1";
}
#progress_header li:nth-child(2):after {
content: "2";
}
#progress_header li:nth-child(3):after {
content: "3";
}
#progress_header li:before {
content: '';
width: 100%;
height: 5px;
background: #fff;
position: absolute;
left: -50%;
top: 50%;
z-index: -1;
}
#progress_header li:first-child:before {
content: none;
}
#progress_header li.active:before,
#progress_header li.active:after {
background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
color: #fff !important;
transition: all 0.5s;
}
/*title*/
.title-box {
width: 100%;
margin: 0 0 30px 0;
}
.title-box h2 {
font-size: 22px;
text-transform: uppercase;
color: #2C3E50;
margin: 0;
font-family: cursive;
display: inline-block;
position: relative;
padding: 0 0 10px 0;
font-family: 'Roboto', sans-serif;
}
.title-box h2:before {
content: "";
background: #6ddc8b;
width: 70px;
height: 2px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
display: block;
}
.title-box h2:after {
content: "";
background: #6ddc8b;
width: 50px;
height: 2px;
position: absolute;
bottom: -5px;
left: 0;
right: 0;
margin: 0 auto;
display: block;
}
/*Input and Button*/
.multistep-box {
background: white;
border: 0 none;
border-radius: 3px;
box-shadow: 1px 1px 55px 3px rgba(255, 255, 255, 0.4);
padding: 30px 30px;
box-sizing: border-box;
width: 80%;
margin: 0 10%;
position: absolute;
}
.multistep-box:not(:first-of-type) {
display: none;
}
.multistep-box p {
margin: 0 0 12px 0;
text-align: left;
}
.multistep-box span {
font-size: 12px;
color: #FF0000;
}
input, textarea {
padding: 15px;
border: 1px solid #ccc;
border-radius: 3px;
margin: 0;
width: 100%;
box-sizing: border-box;
font-family: 'Roboto', sans-serif;
color: #2C3E50;
font-size: 13px;
transition: all 0.5s;
outline: none;
}
input:focus, textarea:focus {
box-shadow: inset 0px 0px 50px 2px rgb(0,0,0,0.1);
}
input.box_error, textarea.box_error {
border-color: #FF0000;
box-shadow: inset 0px 0px 50px 2px rgb(255,0,0,0.1);
}
input.box_error:focus, textarea.box_error:focus {
box-shadow: inset 0px 0px 50px 2px rgb(255,0,0,0.1);
}
p.nxt-prev-button {
margin: 25px 0 0 0;
text-align: center;
}
.action-button {
width: 100px;
font-weight: bold;
color: white;
border: 0 none;
border-radius: 1px;
cursor: pointer;
padding: 10px 5px;
margin: 0 5px;
background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b);
transition: all 0.5s;
}
.action-button:hover,
.action-button:focus {
box-shadow: 0 0 0 2px white, 0 0 0 3px #6ce199;
}
.form_submited #multistep_form {
opacity: 0;
visibility: hidden;
}
.form_submited h1 {
-webkit-background-clip: text;
transform: translate(0%, 0%);
-webkit-transform: translate(0%, 0%);
transition: all 0.3s ease;
opacity: 1;
visibility: visible;
}
h1 {
margin: 0;
text-align: center;
font-size: 90px;
background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
color: transparent;
-webkit-background-clip: text;
-webkit-background-clip: text;
transform: translate(0%, -80%);
-webkit-transform: translate(0%, -80%);
transition: all 0.3s ease;
opacity: 0;
visibility: hidden;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
text-align: center;
top: 50%;
}
h3{
color:#fff;
text-align:center;
margin-bottom:20px;
}
Add jQuery:
var current_slide, next_slide, previous_slide;
var left, opacity, scale;
var animation;
var error = false;
// email validation
$("#email").keyup(function() {
var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
if (!emailReg.test($("#email").val())) {
$("#error-email").text('Please enter an Email addres.');
$("#email").addClass("box_error");
error = true;
} else {
$("#error-email").text('');
error = false;
$("#email").removeClass("box_error");
}
});
// password validation
$("#pass").keyup(function() {
var pass = $("#pass").val();
var cpass = $("#cpass").val();
if (pass != '') {
$("#error-pass").text('');
error = false;
$("#pass").removeClass("box_error");
}
if (pass != cpass && cpass != '') {
$("#error-cpass").text('Password and Confirm Password is not matched.');
error = true;
} else {
$("#error-cpass").text('');
error = false;
}
});
// confirm password validation
$("#cpass").keyup(function() {
var pass = $("#pass").val();
var cpass = $("#cpass").val();
if (pass != cpass) {
$("#error-cpass").text('Please enter the same Password as above.');
$("#cpass").addClass("box_error");
error = true;
} else {
$("#error-cpass").text('');
error = false;
$("#cpass").removeClass("box_error");
}
});
// twitter
$("#twitter").keyup(function() {
var twitterReg = /https?:\/\/twitter\.com\/(#!\/)?[a-z0-9_]+$/;
if (!twitterReg.test($("#twitter").val())) {
$("#error-twitter").text('Twitter link is not valid.');
$("#twitter").addClass("box_error");
error = true;
} else {
$("#error-twitter").text('');
error = false;
$("#twitter").removeClass("box_error");
}
});
// facebook
$("#facebook").keyup(function() {
var facebookReg = /^(https?:\/\/)?(www\.)?facebook.com\/[a-zA-Z0-9(\.\?)?]/;
if (!facebookReg.test($("#facebook").val())) {
$("#error-facebook").text('Facebook link is not valid.');
$("#facebook").addClass("box_error");
error = true;
} else {
$("#error-facebook").text('');
error = false;
$("#facebook").removeClass("box_error");
}
});
// linkedin
$("#linkedin").keyup(function() {
var linkedinReg = /(ftp|http|https):\/\/?(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
if (!linkedinReg.test($("#linkedin").val())) {
$("#error-linkedin").text('Linkedin link is not valid.');
$("#linkedin").addClass("box_error");
error = true;
} else {
$("#error-linkedin").text('');
error = false;
$("#linkedin").removeClass("box_error");
}
});
// first name
$("#fname").keyup(function() {
var fname = $("#fname").val();
if (fname == '') {
$("#error-fname").text('Enter your First name.');
$("#fname").addClass("box_error");
error = true;
} else {
$("#error-fname").text('');
error = false;
}
if ((fname.length <= 2) || (fname.length > 20)) {
$("#error-fname").text("User length must be between 2 and 20 Characters.");
$("#fname").addClass("box_error");
error = true;
}
if (!isNaN(fname)) {
$("#error-fname").text("Only Characters are allowed.");
$("#fname").addClass("box_error");
error = true;
} else {
$("#fname").removeClass("box_error");
}
});
// last name
$("#lname").keyup(function() {
var lname = $("#lname").val();
if (lname != lname) {
$("#error-lname").text('Enter your Last name.');
$("#lname").addClass("box_error");
error = true;
} else {
$("#error-lname").text('');
error = false;
}
if ((lname.length <= 2) || (lname.length > 20)) {
$("#error-lname").text("User length must be between 2 and 20 Characters.");
$("#lname").addClass("box_error");
error = true;
}
if (!isNaN(lname)) {
$("#error-lname").text("Only Characters are allowed.");
$("#lname").addClass("box_error");
error = true;
} else {
$("#lname").removeClass("box_error");
}
});
// phone
$("#phone").keyup(function() {
var phone = $("#phone").val();
if (phone != phone) {
$("#error-phone").text('Enter your Phone number.');
$("#phone").addClass("box_error");
error = true;
} else {
$("#error-phone").text('');
error = false;
}
if (phone.length != 10) {
$("#error-phone").text("Mobile number must be of 10 Digits only.");
$("#phone").addClass("box_error");
error = true;
} else {
$("#phone").removeClass("box_error");
}
});
// address
$("#address").keyup(function() {
var address = $("#address").val();
if (address != address) {
$("#error-address").text('Enter your Address.');
$("#address").addClass("box_error");
error = true;
} else {
$("#error-address").text('');
error = false;
$("#address").removeClass("box_error");
}
});
// first step validation
$(".fs_next_btn").click(function() {
// email
if ($("#email").val() == '') {
$("#error-email").text('Please enter an email address.');
$("#email").addClass("box_error");
error = true;
} else {
var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
if (!emailReg.test($("#email").val())) {
$("#error-email").text('Please insert a valid email address.');
error = true;
} else {
$("#error-email").text('');
$("#email").removeClass("box_error");
}
}
// password
if ($("#pass").val() == '') {
$("#error-pass").text('Please enter a password.');
$("#pass").addClass("box_error");
error = true;
}
if ($("#cpass").val() == '') {
$("#error-cpass").text('Please enter a Confirm password.');
$("#cpass").addClass("box_error");
error = true;
} else {
var pass = $("#pass").val();
var cpass = $("#cpass").val();
if (pass != cpass) {
$("#error-cpass").text('Please enter the same password as above.');
error = true;
} else {
$("#error-cpass").text('');
$("#pass").removeClass("box_error");
$("#cpass").removeClass("box_error");
}
}
// animation
if (!error) {
if (animation) return false;
animation = true;
current_slide = $(this).parent().parent();
next_slide = $(this).parent().parent().next();
$("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");
next_slide.show();
current_slide.animate({
opacity: 0
}, {
step: function(now, mx) {
scale = 1 - (1 - now) * 0.2;
left = (now * 50) + "%";
opacity = 1 - now;
current_slide.css({
'transform': 'scale(' + scale + ')'
});
next_slide.css({
'left': left,
'opacity': opacity
});
},
duration: 800,
complete: function() {
current_slide.hide();
animation = false;
},
easing: 'easeInOutBack'
});
}
});
// second step validation
$(".ss_next_btn").click(function() {
// twitter
if ($("#twitter").val() == '') {
$("#error-twitter").text('twitter link is required.');
$("#twitter").addClass("box_error");
error = true;
} else {
var twitterReg = /https?:\/\/twitter\.com\/(#!\/)?[a-z0-9_]+$/;
if (!twitterReg.test($("#twitter").val())) {
$("#error-twitter").text('Twitter link is not valid.');
error = true;
} else {
$("#error-twitter").text('');
$("#twitter").removeClass("box_error");
}
}
// facebook
if ($("#facebook").val() == '') {
$("#error-facebook").text('Facebook link is required.');
$("#facebook").addClass("box_error");
error = true;
} else {
var facebookReg = /^(https?:\/\/)?(www\.)?facebook.com\/[a-zA-Z0-9(\.\?)?]/;
if (!facebookReg.test($("#facebook").val())) {
$("#error-facebook").text('Facebook link is not valid.');
error = true;
$("#facebook").addClass("box_error");
} else {
$("#error-facebook").text('');
}
}
// linkedin
if ($("#linkedin").val() == '') {
$("#error-linkedin").text('Linkedin link is required.');
$("#linkedin").addClass("box_error");
error = true;
} else {
var linkedinReg = /(ftp|http|https):\/\/?(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
if (!linkedinReg.test($("#linkedin").val())) {
$("#error-linkedin").text('Linkedin link is not valid.');
error = true;
} else {
$("#error-linkedin").text('');
$("#linkedin").removeClass("box_error");
}
}
if (!error) {
if (animation) return false;
animation = true;
current_slide = $(this).parent().parent();
next_slide = $(this).parent().parent().next();
$("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");
next_slide.show();
current_slide.animate({
opacity: 0
}, {
step: function(now, mx) {
scale = 1 - (1 - now) * 0.2;
left = (now * 50) + "%";
opacity = 1 - now;
current_slide.css({
'transform': 'scale(' + scale + ')'
});
next_slide.css({
'left': left,
'opacity': opacity
});
},
duration: 800,
complete: function() {
current_slide.hide();
animation = false;
},
easing: 'easeInOutBack'
});
}
});
// third step validation
$(".ts_next_btn").click(function() {
// first name
if ($("#fname").val() == '') {
$("#error-fname").text('Enter your First name.');
$("#fname").addClass("box_error");
error = true;
} else {
var fname = $("#fname").val();
if (fname != fname) {
$("#error-fname").text('First name is required.');
error = true;
} else {
$("#error-fname").text('');
error = false;
$("#fname").removeClass("box_error");
}
if ((fname.length <= 2) || (fname.length > 20)) {
$("#error-fname").text("User length must be between 2 and 20 Characters.");
error = true;
}
if (!isNaN(fname)) {
$("#error-fname").text("Only Characters are allowed.");
error = true;
} else {
$("#fname").removeClass("box_error");
}
}
// last name
if ($("#lname").val() == '') {
$("#error-lname").text('Enter your Last name.');
$("#lname").addClass("box_error");
error = true;
} else {
var lname = $("#lname").val();
if (lname != lname) {
$("#error-lname").text('Last name is required.');
error = true;
} else {
$("#error-lname").text('');
error = false;
}
if ((lname.length <= 2) || (lname.length > 20)) {
$("#error-lname").text("User length must be between 2 and 20 Characters.");
error = true;
}
if (!isNaN(lname)) {
$("#error-lname").text("Only Characters are allowed.");
error = true;
} else {
$("#lname").removeClass("box_error");
}
}
// phone
if ($("#phone").val() == '') {
$("#error-phone").text('Enter your Phone number.');
$("#phone").addClass("box_error");
error = true;
} else {
var phone = $("#phone").val();
if (phone != phone) {
$("#error-phone").text('Phone number is required.');
error = true;
} else {
$("#error-phone").text('');
error = false;
}
if (phone.length != 10) {
$("#error-phone").text("Mobile number must be of 10 Digits only.");
error = true;
} else {
$("#phone").removeClass("box_error");
}
}
// address
if ($("#address").val() == '') {
$("#error-address").text('Enter your Address.');
$("#address").addClass("box_error");
error = true;
} else {
var address = $("#address").val();
if (address != address) {
$("#error-address").text('Address is required.');
error = true;
} else {
$("#error-address").text('');
$("#address").removeClass("box_error");
error = false;
}
}
if (!error) {
if (animation) return false;
animation = true;
current_slide = $(this).parent().parent();
next_slide = $(this).parent().parent().next();
$("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");
next_slide.show();
current_slide.animate({
opacity: 0
}, {
step: function(now, mx) {
scale = 1 - (1 - now) * 0.2;
left = (now * 50) + "%";
opacity = 1 - now;
current_slide.css({
'transform': 'scale(' + scale + ')'
});
next_slide.css({
'left': left,
'opacity': opacity
});
},
duration: 800,
complete: function() {
current_slide.hide();
animation = false;
},
easing: 'easeInOutBack'
});
}
});
// previous
$(".previous").click(function() {
if (animation) return false;
animation = true;
current_slide = $(this).parent().parent();
previous_slide = $(this).parent().parent().prev();
$("#progress_header li").eq($(".multistep-box").index(current_slide)).removeClass("active");
previous_slide.show();
current_slide.animate({
opacity: 0
}, {
step: function(now, mx) {
scale = 0.8 + (1 - now) * 0.2;
left = ((1 - now) * 50) + "%";
opacity = 1 - now;
current_slide.css({
'left': left
});
previous_slide.css({
'transform': 'scale(' + scale + ')',
'opacity': opacity
});
},
duration: 800,
complete: function() {
current_slide.hide();
animation = false;
},
easing: 'easeInOutBack'
});
});
$(".submit_btn").click(function() {
if (!error){
$(".main").addClass("form_submited");
}
return false;
})
Output:
Original article source at: https://websolutionstuff.com/
1671787380
Whether you are a programmer, a visual artist, a media pundit, or a niche aficionado, online communities are there for you. Since the offer online is huge, more and more people are joining as we speak.
By now everyone is part of one and finds at least one reason to be there. But good online communities that grow as time passes are special.
If you want to get all the benefits from them, you got to know about how to actually build them big and strong.
In today’s article, we will do just that by mentioning 9 clever methods/considerations to keep in mind.
Let’s start.
First and foremost, you got to make sure you understand your audience well. This includes its preferred interests, language and tone, as well as what influences it, etc.
This point is so crucial because you’re not there to pretend you care about them but to honestly engage.
Once you get to know your audience, everything you do, from the silliest comment, will be better received. And by extension, you and they will have a vibrant community on your hands.
There are various ways to gain an understanding of it. For example, you could research by looking at blogs and groups, or by using keywords.
Another main method is to simply experiment a bit. There won’t be a way to exactly know them until you go through experiences together. Like in physical relationships, experience and history together play a big role in enduring the bond.
When you get to know your audience, you will start to see some of their problems. Then, we recommend digging deeper and identifying as many pressing issues as possible.
You aim for variety and large numbers because you want to pick a few and provide a solution to them.
For example, some problems may have to do with their relationship with their parents. Here, there isn’t a great deal you can do, besides entertain and extending a community to them.
But imagine if someone is having trouble ascending on their career. And you, because of your knowledge of X skill/field, give them a clear and substantive solution. That’s what we are aiming at here.
When you provide substantive solutions, you are giving real value in the process. So, in a nutshell, real value is nothing more than contributing to others in an important fashion.
A clear example of non-real value is any type of generic content made with the sole purpose of profit. This type of content lacks originality, authenticity and treats its target audience as cash cows.
But of course, you won’t do that because even if you’re not changing someone’s life every time. Because you provide solutions and give what people want/need, doing so with passion, value emerges naturally.
Even if you are your community’s only source of joy, you must engage regularly to keep it. Again, just like with any relationship, contact (of a positive kind) strengthens them.
One great fact is that interactions come in many shapes and forms online. You count with polls, posts, comments, videos, asking for submissions, reviews, Q&A, lives, etc.
And very importantly as well, not everything has to be connected with your content. For example, you could spend a few hours a month on a casual chit-chat and still be making your community stronger.
Of course, to interact you need a space. Thankfully, the online world again gives you many great options for all kinds of folks.
Let’s talk about FB groups for a moment since they are very popular and quite long living. On them, you will see people posting, commenting, and engaging often. Even those that only like or read get into the community and absorb some of its particularities.
For example, these platforms often allow for internal language, jokes, references, to emerge. And everyone, in one way or the other, gets influenced by it. Which is exactly what you are aiming at.
One piece of advice, make sure to pick one platform that fits well with your audience and content. Reddit is not the same as Tumblr, nor as FB.
Though we tend to like keeping our circles strong and small, it is better to keep expansion always in mind here. By that, we don’t mean a type of imperialist mission, but only a healthy dose of ambition.
Niche small communities are lovely, yes, but you could enlarge it a bit without sacrificing its likeability. Besides, consider that even if you open it for the broad public, not everyone will join. Only those that somehow feel attracted to it will do, which will maintain the essence of the community intact.
Now, there are 2 main ways to grow it. One comes almost naturally as you engage with it and continue to provide value. People will realize that and spread the word, forming a healthy feedback loop.
The other method is to pursue it. For example, you can ask your audience to spread the word. Or do it yourself, via posts in other sites to drag some audiences, collabs, giveaways, competitions, etc.
We all understand that making a few bucks as a reward for the great work and value is fair. But, if you are thinking about building a community, it is not a good step to take.
This is because it simply gives the impression that you are in it for the cash. It could be even worse if people think you “were faking all along”. Don’t get offended if you encounter some of those presumptions. We have to admit the internet is full of people that only care about money, so suspicion is natural, we even suffer from it.
But please, don’t get discouraged and believe that a strong community and money cannot go together.
They surely can, and the way to go is to turn that step of “converting” into a natural transition. If you are giving great value and count with a nice community, sales will follow if you intend it.
Besides, if you get along well, you might get some contributions based on that alone. Just picture a friend of yours starting a business, you will at least contribute once to him/her, even if you don’t like the project. Now imagine, you are that friend of your audience, but they do like what you are giving them. Why wouldn’t they monetary contribute/repay?
With that realization in mind, what follows is only to direct them to your sales funnel. Which is the process of securing a sale, from the very start to the end. An example online starts with reading a post, then clicking on a site, following, and later converting.
Besides, when you incorporate them into this process, you are not asking for money alone. You are saying, “I got this valuable product, and I believe you know I deserve some credit for it”. A stronger basis to build a mutually beneficial relationship than this escapes me.
Lastly, another method that is connected to the one about interaction, is to do this. Events, of all kinds, work as opportunities to bring more people together than usual. And let the distinct features of the community thrive.
We are a social species; our communities are places that give us a sense of belonging and a bigger purpose. What better way to go than allowing spaces for this to flourish?
Besides, they can be great spaces to have fun, further get to know them, and even sell merch!
Regardless of where you see them, events are a must.
A key takeaway is to remember 4 words: genuine, knowing, value, interaction. If they become your mantra, you will succeed with your mission of building a strong community. And we are certain that, all those 4 are more than familiar to you.
Thanks for reading this article, we hope it has been helpful and wish you all good luck as always.
Original article source at: https://www.blog.duomly.com/
1671730920
Forms with multiple steps are common in web applications, but they can be difficult to organize and manage the data. Using reactive forms in Angular, however, it can be less painful. In this article we'll take a look at one way to manage multi step forms in your Angular app.
For this article, we'll use an example form with multiple steps. This sample will have just two steps, but the principles here can be applied to forms with more steps as well. The first of our two steps gathers the name, address, and phone number of a user. The second step gets their email, username, and password. We'll talk about how to split the form up into two parts and how to submit the form when finished.
Before we make the component to manage the first step of the form, the step that gathers the personal information for the user, we need to create a container component which will hold the two form steps. This is where we will manage the data from the two subforms, as well as manage which step the user is on or wants to be on. Finally, we'll also submit the completed form from this component as well. So, for the first step, create a new component:
$ ng g c form-container
Inside the component, add the following content:
type Step = 'personalInfo' | 'loginInfo';
export class FormContainerComponent implements OnInit {
private currentStepBs: BehaviorSubject<Step> = new BehaviorSubject<Step>('personalInfo');
public currentStep$: Observable<Step> = this.currentStepBs.asObservable();
public userForm: FormGroup;
constructor(private _fb: FormBuilder) {}
ngOnInit() {
this.userForm = this._fb.group({
personalInfo: null,
loginInfo: null
});
}
subformInitialized(name: string, group: FormGroup) {
this.userForm.setControl(name, group);
}
changeStep(currentStep: string, direction: 'forward' | 'back') {
switch(currentStep) {
case 'personalInfoStep':
if (direction === 'forward') {
this.currentStepBs.next('loginInfo');
}
break;
case 'loginInfoStep':
if (direction === 'back') {
this.currentStepBs.next('personalInfo');
}
break;
}
}
submitForm() {
const formValues = this.userForm.value;
// submit the form with a service
}
}
This is the majority of the component's class file. It's all pretty straightforward, but let's break it down real quick. First, a BehaviorSubject
is created, currentStepBs
to keep track of which form step to show. The type is declared right before the component class, and is called Step
. The possible values are strings representing the two steps. If you have more than two steps, you can provide one string for each step. Next, the userForm
variable is created, but not initialized. That is done in the ngOnInit
method. In the method, the form is intialized as a FormGroup
with two attributes, one for each step of the form. Their value is initially null
, but that will be updated when the step components appear on the screen. Next up is a method, subformInitialized
which will be called when a form step is created and appears on the page. The method sets the control on the main userForm
. Next is a method that changes the current step. It is pretty simple; it calls the next method on the BehaviorSubject
and passes in the correct step. The last method is where the actual form submission will be done. This is where the form value will be used and passed into a service method to submit the value of the form.
Before moving on to the next section, let's look at the component's HTML file briefly. There won't be a lot here, but it will be good to look at.
<ng-container [ngSwitch]="currentStep$ | async">
<app-personal-info
*ngSwitchCase="'personalInfo'"
[startingForm]="userForm.get('personalInfo').value"
(subformInitialized)="subformInitialized('personalInfo', $event)"
(changeStep)="changeStep('personalInfoStep', $event)"
></app-personal-info>
<app-login-info
*ngSwitchCase="'loginInfo'"
[startingForm]="userForm.get('loginInfo').value"
(subformInitialized)="subformInitialized('loginInfo', $event)"
(changeStep)="changeStep('loginInfoStep', 'back')"
(submitForm)="submitForm()"
></app-login-info>
</ng-container>
The two components that manage each step of the form are on the page. Only one is visible at a time, and they each get a starting value (to show previously entered information if they go back a step), as well as have Output
s for the subform being initialized and changing steps. The second step also has a submitForm
output.
Now that our container component is created and everything is set up, we can move on to creating the PersonalInfo
component, which will contain the form to gather the personal info.
Let's begin by creating the component for the personal info step:
$ ng g c personal-info
Next, let's look at the class file for the component.
export class PersonalInfoComponent implements OnInit {
@Input() startingForm: FormGroup;
@Output() subformInitialized: EventEmitter<FormGroup> = new EventEmitter<FormGroup>();
@Output() changeStep: EventEmitter<boolean> = new EventEmitter<boolean>();
public personalInfoForm: FormGroup;
constructor(private _fb: FormBuilder) {}
ngOnInit() {
if (this.startingForm) {
this.personalInfoForm = this.startingForm;
} else {
this.personalInfoForm = this._fb.group({
firstName: '',
lastName: '',
// ... continue with the other fields
})
}
this.subformInitialized.emit(this.personalInfoForm);
}
doChangeStep(direction: 'forward') {
this.changeStep.emit(direction);
}
}
This component is a lot more simple than the container component. A form is created in the ngOnInit
method, either from the data that's passed in to the component or with the FormBuilder
. After the form is created, its value is emitted to the parent so the form values can be tracked in the parent component. There's a doChangeStep
method that will emit forward and move the user to the next step. The template of the component will just be the form inputs as well as a "Next" button.
We won't cover creating the login info component, but it will be the same as this component, but will only allow the user to go back, and will allow the user to click a submit button to take them on to the next portion of the app after they've filled out the forms.
This method of managing complex forms has worked out really well for us. By organizing the forms like this each step can use its own reactive form with form validation and all of the other benefits of reactive forms. The parent container controls all the data from each step automatically. When it's time to submit the form, each step's data is contained in a separate attribute of the form.value
object on the parent component. We've been able to easily add custom validators to the forms (one of my favorite features of reactive forms) which would have been more difficult with template driven forms.
This is also just one way to manage multi step forms in Angular. I'm sure you can achieve the same result with template driven forms, or in other manners. This way has been pretty straightforward and not overly complex. Most importantly, it worked for our situation. Hopefully it helps you as well!
Original article source at: https://www.prestonlamb.com
1670585767
ADAPTing Agile development, as the leading Agile speaker and author Mike Cohn describes “how one can iterate toward increased agility by combining a senior-level guiding coalition with multiple action teams.”
To successfully implement a change, senior leadership need to create a sense of urgency in their organization so that employees, lower-level managers and other impacted stakeholders are on board. It will help gain confidence and cooperation of the teams.
Following 5 steps are necessary for ADAPTing to Agile development:
For successful Agile transition, it’s very important the each individual moves through Awareness, Desire and Ability stages. Using the Promote stage of Agile transition, early adopters of Agile can build awareness and desire amongst the late adopters of Agile. Lastly its utmost important to Transfer the implication and impact of Agile to other groups throughout the organisation like Project management office, sales, information technology, operations, hardware development; otherwise the Agile transition will fail inevitably.
Lets go through the wife stages for ADAPTing to Agile development
When it comes to achieving desired results, it’s important to be aware of the current process / practise and that its efficiency can be improved. A change in approach begins with recognizing that the current approach has a room for improvement. However, it can be difficult to embrace that what worked in the past may not work in the present. One of the most common reasons people don’t become aware of the need to change is because they don’t have enough exposure to the big picture. In this Awareness stage, Rather than listing a variety of common project problems, focus on the two or three major problems that reflect the need for change.
Some Tools to Create awareness:
There needs to be a strong desire to adopt Agile as a way to address the current problems. Not only must we recognize the need for change, but we must also have the desire to change. Many people find it difficult to move from the realization that the current development process isn’t working to the desire to use a different one.
Desire Tools:
If you want to be successful, you need to be able to be agile. To be successful with Agile, team members must learn new skills and overcome old ones. Some of the larger challenges that Agile teams will face includes: Learning new technical skills, think and work as an Agile team and delivering working product frequently.
Ability Tools:
Promote by sharing our experiences with Agile, we can help others remember and learn from our successes. By publicizing current successes with Agile, you’ll quickly start creating awareness for the next round of improvements. This stage also help teams maintain Agile behavior by sharing the successes that teams have achieved. Finally, publicizing Agile success help those not directly involved in adopting Agile to become more aware and interested in it.
Promote Tools:
It is impossible for a development team to remain Agile without help from the rest of the company. If the impact of using Agile is not transferred to other departments, the organizational focus from those departments will eventually stall and stifle transition efforts. The rest of the organization must be able to work effectively with Agile.
There are other departments that must also be aware of the effects of Agile on their work. Many groups with an organizational gravity, such as the project management office, sales, information technology, operations, hardware development, and others, contribute to the success of a business. The impact of Agile for long-term success will be important to consider.
Visit Knolway to access more resources.
Original article source at: https://blog.knoldus.com/
1661392920
StepIndicator is a package that lets you create progressing timeline easier with number of steps and easy to customize.
int count; double indicatorRadius; int currentStep; Color indicatorColor; Color activeIndicatorColor; double lineWidth; double lineHeight; double lineRadius; Color lineColor; Color activeLineColor; Color indicatorBorderColor; double indicatorBorderWidth; final TextStyle numberStyle; TextStyle activeNumberStyle; double lineSpacing; List stepTitles; bool enableStepTitle; TextStyle stepTitleStyle;
import 'package:step_indicator/step_indicator.dart';
Run this command:
With Flutter:
$ flutter pub add step_indicator
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
step_indicator: ^0.0.1
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:step_indicator/step_indicator.dart';
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:step_indicator/step_indicator.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const StepIndicator(
currentStep: 0,
count: 4,
activeIndicatorColor: Colors.blue,
activeLineColor: Colors.blue,
enableStepTitle: true,
indicatorBorderWidth: 2,
stepTitles: ["Step 1", "Step 2", "Step 3", "Step 4"],
stepTitleStyle: TextStyle(
color: Colors.black,
),
);
}
}
Author: Veasnawt
Source Code: https://github.com/veasnawt/step_indicator
License: MIT license
1659578460
A progress widget for showing a proccess completed step by step
Example One
import 'package:flutter/material.dart';
import 'package:step_progress/step_progress.dart';
class ExampleOne extends StatefulWidget {
ExampleOne({Key? key}) : super(key: key);
@override
_ExampleOneState createState() => _ExampleOneState();
}
class _ExampleOneState extends State<ExampleOne> {
final PageController _pageController = PageController();
final StepProgressController _stepProgressController =
StepProgressController(initialStep: 0, totalStep: 4);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example One'),
),
body: Column(children: [
Progress(
stepProgressController: _stepProgressController,
strokeColor: Color(0xff04A7B8),
valueColor: Colors.white,
backgroundColor: Color(0xff04A7B5),
tickColor: Color(0xff04A7B5),
onStepChanged: (index) {
print('on step changed: $index');
},
),
Expanded(
child: PageView(
controller: _pageController,
children: [
Container(
color: Colors.white,
),
Container(
color: Colors.grey[100],
),
Container(
color: Colors.grey[300],
),
Container(
color: Colors.grey[500],
),
],
),
),
]),
floatingActionButton: Align(
alignment: Alignment.bottomCenter,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
onPressed: () {
_pageController.previousPage(
duration: Duration(milliseconds: 300),
curve: Curves.easeInCubic);
_stepProgressController.prevStep();
},
tooltip: 'Back',
child: Text("Back"),
),
SizedBox(
width: 30,
),
FloatingActionButton(
onPressed: () {
_pageController.nextPage(
duration: Duration(milliseconds: 300),
curve: Curves.easeInCubic);
_stepProgressController.nextStep();
},
tooltip: 'Next',
child: Text("Next"),
),
],
),
),
);
}
}
Example Two
import 'package:flutter/material.dart';
import 'package:step_progress/step_progress.dart';
class ExampleTwo extends StatefulWidget {
ExampleTwo({Key? key}) : super(key: key);
@override
_ExampleTwoState createState() => _ExampleTwoState();
}
class _ExampleTwoState extends State<ExampleTwo> {
final StepProgressController _stepProgressController =
StepProgressController(totalStep: 10);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example Two'),
bottom: PreferredSize(
preferredSize: Size(double.infinity, 50),
child: Progress(
stepProgressController: _stepProgressController,
),
),
),
body: Container(
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () {
setState(() {
_stepProgressController.prevStep();
});
},
child: Text("Prev")),
SizedBox(
width: 30,
),
ElevatedButton(
onPressed: () {
setState(() {
_stepProgressController.nextStep();
});
},
child: Text("Next")),
],
),
),
),
);
}
}
Run this command:
With Flutter:
$ flutter pub add step_progress
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
step_progress: ^0.0.2
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:step_progress/step_progress.dart';
example/lib/main.dart
import 'package:flutter/material.dart';
import 'example_one/example_one.dart';
import 'example_two/example_two.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlutterStepProgressDemo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (_) => ExampleOne()));
},
child: Text('Example One')),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (_) => ExampleTwo()));
},
child: Text('Example Two')),
],
),
),
);
}
}
Author: TalebRafiepour
Source Code: https://github.com/TalebRafiepour/step-progress
License: MIT license
1656940560
This is the Serverless Framework plugin for AWS Step Functions.
Serverless Framework v2.32.0 or later is required.
Run npm install
in your Serverless project.
$ npm install --save-dev serverless-step-functions
Add the plugin to your serverless.yml file
plugins:
- serverless-step-functions
Specify your state machine definition using Amazon States Language in a definition
statement in serverless.yml. You can use CloudFormation intrinsic functions such as Ref
and Fn::GetAtt
to reference Lambda functions, SNS topics, SQS queues and DynamoDB tables declared in the same serverless.yml
. Since Ref
returns different things (ARN, ID, resource name, etc.) depending on the type of CloudFormation resource, please refer to this page to see whether you need to use Ref
or Fn::GetAtt
.
Alternatively, you can also provide the raw ARN, or SQS queue URL, or DynamoDB table name as a string. If you need to construct the ARN by hand, then we recommend to use the serverless-pseudo-parameters plugin together to make your life easier.
In addition, if you want to reference a DynamoDB table managed by an external CloudFormation Stack, as long as that table name is exported as an output from that stack, it can be referenced by importing it using Fn::ImportValue
. See the ddbtablestepfunc
Step Function definition below for an example.
functions:
hello:
handler: handler.hello
stepFunctions:
stateMachines:
hellostepfunc1:
events:
- http:
path: gofunction
method: GET
- schedule:
rate: rate(10 minutes)
enabled: true
input:
key1: value1
key2: value2
stageParams:
stage: dev
name: myStateMachine
definition:
Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function"
StartAt: HelloWorld1
States:
HelloWorld1:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
End: true
dependsOn: CustomIamRole
tags:
Team: Atlantis
alarms:
topics:
ok: arn:aws:sns:us-east-1:1234567890:NotifyMe
alarm: arn:aws:sns:us-east-1:1234567890:NotifyMe
insufficientData: arn:aws:sns:us-east-1:1234567890:NotifyMe
metrics:
- executionsTimedOut
- executionsFailed
- executionsAborted
- metric: executionThrottled
treatMissingData: breaching # overrides below default
- executionsSucceeded
treatMissingData: ignore # optional
hellostepfunc2:
definition:
StartAt: HelloWorld2
States:
HelloWorld2:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
End: true
ddbtablestepfunc:
definition:
Comment: Demonstrates how to reference a DynamoDB Table Name exported from an external CloudFormation Stack
StartAt: ImportDDBTableName
States:
ImportDDBTableName:
Type: Task
Resource: "arn:aws:states:::dynamodb:updateItem"
Parameters:
TableName:
Fn::ImportValue: MyExternalStack:ToDoTable:Name # imports a table name from an external stack
Key:
id:
S.$: "$.todoId"
UpdateExpression: "SET #status = :updatedStatus"
ExpressionAttributeNames:
"#status": status
ExpressionAttributeValues:
":updatedStatus":
S: DONE
End: true
dependsOn:
- DynamoDBTable
- KinesisStream
- CustomIamRole
tags:
Team: Atlantis
activities:
- myTask
- yourTask
validate: true # enable pre-deployment definition validation (disabled by default)
plugins:
- serverless-step-functions
- serverless-pseudo-parameters
In the example above, notice that we used Fn::GetAtt: [hello, Arn]
to get the ARN for the hello
function defined earlier. This means you don't have to know how the Serverless
framework converts these local names to CloudFormation logical IDs (e.g. hello-world
becomes HelloDashworldLambdaFunction
).
However, if you prefer to work with logical IDs, you can. You can also express the above Fn::GetAtt
function as Fn::GetAtt: [HelloLambdaFunction, Arn]
. If you're unfamiliar with the convention the Serverless
framework uses, then the easiest thing to do is to first run sls package
then look in the .serverless
folder for the generated CloudFormation template. Here you can find the logical resource names for the functions you want to reference.
In case you need to interpolate a specific stage or service layer variable as the stateMachines name you can add a name
property to your yaml.
service: messager
functions:
sendMessage:
handler: handler.sendMessage
stepFunctions:
stateMachines:
sendMessageFunc:
name: sendMessageFunc-${self:custom.service}-${opt:stage}
definition:
<your definition>
plugins:
- serverless-step-functions
You can use a custom logical id that is only unique within the stack as opposed to the name that needs to be unique globally. This can make referencing the state machine easier/simpler because you don't have to duplicate the interpolation logic everywhere you reference the state machine.
service: messager
functions:
sendMessage:
handler: handler.sendMessage
stepFunctions:
stateMachines:
sendMessageFunc:
id: SendMessageStateMachine
name: sendMessageFunc-${self:custom.service}-${opt:stage}
definition:
<your definition>
plugins:
- serverless-step-functions
You can then Ref: SendMessageStateMachine
in various parts of CloudFormation or serverless.yml
If your state machine depends on another resource defined in your serverless.yml
then you can add a dependsOn
field to the state machine definition
. This would add the DependsOn
clause to the generated CloudFormation template.
This dependsOn
field can be either a string, or an array of strings.
stepFunctions:
stateMachines:
myStateMachine:
dependsOn: myDB
myOtherStateMachine:
dependsOn:
- myOtherDB
- myStream
There are some practical cases when you would like to prevent state machine from deletion on stack delete or update. This can be achieved by adding retain
property to the state machine section.
stepFunctions:
stateMachines:
myStateMachine:
retain: true
Configuring in such way adds "DeletionPolicy" : "Retain"
to the state machine within CloudFormation template.
It's common practice to want to monitor the health of your state machines and be alerted when something goes wrong. You can either:
alarms
configuration from this plugin, which gives you an opinionated set of default alarms (see below)stepFunctions:
stateMachines:
myStateMachine:
alarms:
topics:
ok: arn:aws:sns:us-east-1:1234567890:NotifyMe
alarm: arn:aws:sns:us-east-1:1234567890:NotifyMe
insufficientData: arn:aws:sns:us-east-1:1234567890:NotifyMe
metrics:
- executionsTimedOut
- executionsFailed
- executionsAborted
- executionThrottled
- executionsSucceeded
treatMissingData: missing
Both topics
and metrics
are required properties. There are 4 supported metrics, each map to the CloudWatch Metrics that Step Functions publishes for your executions.
You can configure how the CloudWatch Alarms should treat missing data:
missing
(AWS default): The alarm does not consider missing data points when evaluating whether to change state.ignore
: The current alarm state is maintained.breaching
: Missing data points are treated as breaching the threshold.notBreaching
: Missing data points are treated as being within the threshold.For more information, please refer to the official documentation.
The generated CloudWatch alarms would have the following configurations:
namespace: 'AWS/States'
metric: <ExecutionsTimedOut | ExecutionsFailed | ExecutionsAborted | ExecutionThrottled>
threshold: 1
period: 60
evaluationPeriods: 1
ComparisonOperator: GreaterThanOrEqualToThreshold
Statistic: Sum
treatMissingData: <missing (default) | ignore | breaching | notBreaching>
Dimensions:
- Name: StateMachineArn
Value: <ArnOfTheStateMachine>
You can also override the default treatMissingData
setting for a particular alarm by specifying an override:
alarms:
topics:
ok: arn:aws:sns:us-east-1:1234567890:NotifyMe
alarm: arn:aws:sns:us-east-1:1234567890:NotifyMe
insufficientData: arn:aws:sns:us-east-1:1234567890:NotifyMe
metrics:
- executionsTimedOut
- executionsFailed
- executionsAborted
- metric: executionThrottled
treatMissingData: breaching # override
- executionsSucceeded
treatMissingData: ignore # default
By default, the CloudFormation assigns names to the alarms based on the CloudFormation stack and the resource logical Id, and in some cases and these names could be confusing.
To use custom names to the alarms add nameTemplate
property in the alarms
object.
example:
service: myservice
plugins:
- serverless-step-functions
stepFunctions:
stateMachines:
main-workflow:
name: main
alarms:
nameTemplate: $[stateMachineName]-$[cloudWatchMetricName]-alarm
topics:
alarm: !Ref AwsAlertsGenericAlarmTopicAlarm
metrics:
- executionsFailed
- executionsAborted
- executionsTimedOut
- executionThrottled
treatMissingData: ignore
definition: ${file(./step-functions/main.asl.yaml)}
Supported variables to the nameTemplate
property:
stateMachineName
metricName
cloudWatchMetricName
Per-Metric Alarm Name
To overwrite the alarm name for a specific metric, add the alarmName
property in the metric object.
service: myservice
plugins:
- serverless-step-functions
stepFunctions:
stateMachines:
main-workflow:
name: main
alarms:
nameTemplate: $[stateMachineName]-$[cloudWatchMetricName]-alarm
topics:
alarm: !Ref AwsAlertsGenericAlarmTopicAlarm
metrics:
- metric: executionsFailed
alarmName: mycustom-name-${self:stage.region}-Failed-alarm
- executionsAborted
- executionsTimedOut
- executionThrottled
treatMissingData: ignore
definition: ${file(./step-functions/main.asl.yaml)}
You can monitor the execution state of your state machines via CloudWatch Events. It allows you to be alerted when the status of your state machine changes to ABORTED
, FAILED
, RUNNING
, SUCCEEDED
or TIMED_OUT
.
You can configure CloudWatch Events to send notification to a number of targets. Currently this plugin supports sns
, sqs
, kinesis
, firehose
, lambda
and stepFunctions
.
To configure status change notifications to your state machine, you can add a notifications
like below:
stepFunctions:
stateMachines:
hellostepfunc1:
name: test
definition:
...
notifications:
ABORTED:
- sns: SNS_TOPIC_ARN
- sqs: SQS_TOPIC_ARN
- sqs: # for FIFO queues, which requires you to configure the message group ID
arn: SQS_TOPIC_ARN
messageGroupId: 12345
- lambda: LAMBDA_FUNCTION_ARN
- kinesis: KINESIS_STREAM_ARN
- kinesis:
arn: KINESIS_STREAM_ARN
partitionKeyPath: $.id # used to choose the parition key from payload
- firehose: FIREHOSE_STREAM_ARN
- stepFunctions: STATE_MACHINE_ARN
FAILED:
... # same as above
... # other status
As you can see from the above example, you can configure different notification targets for each type of status change. If you want to configure the same targets for multiple status changes, then consider using YML anchors to keep your YML succinct.
CloudFormation intrinsic functions such as Ref
and Fn::GetAtt
are supported.
When setting up a notification target against a FIFO SQS queue, the queue must enable the content-based deduplication option and you must configure the messageGroupId
.
To implement a blue-green deployment with Step Functions you need to reference the exact versions of the functions.
To do this, you can specify useExactVersion: true
in the state machine.
stepFunctions:
stateMachines:
hellostepfunc1:
useExactVersion: true
definition:
...
By default, your state machine definition will be validated during deployment by StepFunctions. This can be cumbersome when developing because you have to upload your service for every typo in your definition. In order to go faster, you can enable pre-deployment validation using asl-validator which should detect most of the issues (like a missing state property).
stepFunctions:
validate: true
Disables the generation of outputs in the CloudFormation Outputs section. If you define many state machines in serverless.yml you may reach the CloudFormation limit of 60 outputs. If you define noOutput: true
then this plugin will not generate outputs automatically.
stepFunctions:
noOutput: true
At re:invent 2019, AWS introduced Express Workflows as a cheaper, more scalable alternative (but with a cut-down set of features). See this page for differences between standard and express workflows.
To declare an express workflow, specify type
as EXPRESS
and you can specify the logging configuration:
stepFunctions:
stateMachines:
hellostepfunc1:
type: EXPRESS
loggingConfig:
level: ERROR
includeExecutionData: true
destinations:
- Fn::GetAtt: [MyLogGroup, Arn]
You can enable CloudWatch Logs for standard Step Functions, the syntax is exactly like with Express Workflows.
stepFunctions:
stateMachines:
hellostepfunc1:
loggingConfig:
level: ERROR
includeExecutionData: true
destinations:
- Fn::GetAtt: [MyLogGroup, Arn]
You can enable X-Ray for your state machine, specify tracingConfig
as shown below.
stepFunctions:
stateMachines:
hellostepfunc1:
tracingConfig:
enabled: true
Please keep this gotcha in mind if you want to reference the name
from the resources
section. To generate Logical ID for CloudFormation, the plugin transforms the specified name in serverless.yml based on the following scheme.
-
into Dash_
into UnderscoreIf you want to use variables system in name statement, you can't put the variables as a prefix like this:${self:service}-${opt:stage}-myStateMachine
since the variables are transformed within Output section, as a result, the reference will be broken.
The correct sample is here.
stepFunctions:
stateMachines:
myStateMachine:
name: myStateMachine-${self:service}-${opt:stage}
...
resources:
Outputs:
myStateMachine:
Value:
Ref: MyStateMachineDash${self:service}Dash${opt:stage}
To create HTTP endpoints as Event sources for your StepFunctions statemachine
This setup specifies that the hello state machine should be run when someone accesses the API gateway at hello via a GET request.
Here's an example:
stepFunctions:
stateMachines:
hello:
events:
- http:
path: hello
method: GET
definition:
Here You can define an POST endpoint for the path posts/create.
stepFunctions:
stateMachines:
hello:
events:
- http:
path: posts/create
method: POST
definition:
Step Functions have custom actions like DescribeExecution or StopExecution to fetch and control them. You can use custom actions like this:
stepFunctions:
stateMachines:
start:
events:
- http:
path: action/start
method: POST
definition:
...
status:
events:
- http:
path: action/status
method: POST
action: DescribeExecution
definition:
...
stop:
events:
- http:
path: action/stop
method: POST
action: StopExecution
definition:
...
Request template is not used when action is set because there're a bunch of actions. However if you want to use request template you can use Customizing request body mapping templates.
The plugin would generate an IAM Role for you by default. However, if you wish to use an IAM role that you have provisioned separately, then you can override the IAM Role like this:
stepFunctions:
stateMachines:
hello:
events:
- http:
path: posts/create
method: POST
iamRole: arn:aws:iam::<accountId>:role/<roleName>
definition:
You can share the same API Gateway between multiple projects by referencing its REST API ID and Root Resource ID in serverless.yml as follows:
service: service-name
provider:
name: aws
apiGateway:
# REST API resource ID. Default is generated by the framework
restApiId: xxxxxxxxxx
# Root resource, represent as / path
restApiRootResourceId: xxxxxxxxxx
functions:
...
If your application has many nested paths, you might also want to break them out into smaller services.
However, Cloudformation will throw an error if we try to generate an existing path resource. To avoid that, we reference the resource ID:
service: service-a
provider:
apiGateway:
restApiId: xxxxxxxxxx
restApiRootResourceId: xxxxxxxxxx
# List of existing resources that were created in the REST API. This is required or the stack will be conflicted
restApiResources:
/users: xxxxxxxxxx
functions:
...
Now we can define endpoints using existing API Gateway ressources
stepFunctions:
stateMachines:
hello:
events:
- http:
path: users/create
method: POST
To set CORS configurations for your HTTP endpoints, simply modify your event configurations as follows:
stepFunctions:
stateMachines:
hello:
events:
- http:
path: posts/create
method: POST
cors: true
definition:
Setting cors to true assumes a default configuration which is equivalent to:
stepFunctions:
stateMachines:
hello:
events:
- http:
path: posts/create
method: POST
cors:
origin: '*'
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
- X-Amz-User-Agent
allowCredentials: false
definition:
Configuring the cors property sets Access-Control-Allow-Origin, Access-Control-Allow-Headers, Access-Control-Allow-Methods,Access-Control-Allow-Credentials headers in the CORS preflight response. To enable the Access-Control-Max-Age preflight response header, set the maxAge property in the cors object:
stepFunctions:
stateMachines:
SfnApiGateway:
events:
- http:
path: /playground/start
method: post
cors:
origin: '*'
maxAge: 86400
If you want to require that the caller submit the IAM user's access keys in order to be authenticated to invoke your Lambda Function, set the authorizer to AWS_IAM as shown in the following example:
stepFunctions:
stateMachines:
hello:
events:
- http:
path: posts/create
method: POST
authorizer: aws_iam
definition:
Custom Authorizers allow you to run an AWS Lambda Function before your targeted AWS Lambda Function. This is useful for Microservice Architectures or when you simply want to do some Authorization before running your business logic.
You can enable Custom Authorizers for your HTTP endpoint by setting the Authorizer in your http event to another function in the same service, as shown in the following example:
stepFunctions:
stateMachines:
hello:
- http:
path: posts/create
method: post
authorizer: authorizerFunc
definition:
If the Authorizer function does not exist in your service but exists in AWS, you can provide the ARN of the Lambda function instead of the function name, as shown in the following example:
stepFunctions:
stateMachines:
hello:
- http:
path: posts/create
method: post
authorizer: xxx:xxx:Lambda-Name
definition:
Auto-created Authorizer is convenient for conventional setup. However, when you need to define your custom Authorizer, or use COGNITO_USER_POOLS authorizer with shared API Gateway, it is painful because of AWS limitation. Sharing Authorizer is a better way to do.
stepFunctions:
stateMachines:
createUser:
...
events:
- http:
path: /users
...
authorizer:
# Provide both type and authorizerId
type: COGNITO_USER_POOLS # TOKEN, CUSTOM or COGNITO_USER_POOLS, same as AWS Cloudformation documentation
authorizerId:
Ref: ApiGatewayAuthorizer # or hard-code Authorizer ID
# [Optional] you can also specify the OAuth scopes for Cognito
scopes:
- scope1
...
The plugin generates default body mapping templates for application/json
and application/x-www-form-urlencoded
content types. The default template would pass the request body as input to the state machine. If you need access to other contextual information about the HTTP request such as headers, path parameters, etc. then you can also use the lambda_proxy
request template like this:
stepFunctions:
stateMachines:
hello:
events:
- http:
path: posts/create
method: POST
request:
template: lambda_proxy
This would generate the normal LAMBDA_PROXY template used for API Gateway integration with Lambda functions.
If you'd like to add content types or customize the default templates, you can do so by including your custom API Gateway request mapping template in serverless.yml
like so:
stepFunctions:
stateMachines:
hello:
events:
- http:
path: posts/create
method: POST
request:
template:
application/json: |
#set( $body = $util.escapeJavaScript($input.json('$')) )
#set( $name = $util.escapeJavaScript($input.json('$.data.attributes.order_id')) )
{
"input": "$body",
"name": "$name",
"stateMachineArn":"arn:aws:states:#{AWS::Region}:#{AWS::AccountId}:stateMachine:processOrderFlow-${opt:stage}"
}
name: processOrderFlow-${opt:stage}
definition:
If you'd like to add custom headers in the HTTP response, or customize the default response template (which just returns the response from Step Function's StartExecution API), then you can do so by including your custom headers and API Gateway response mapping template in serverless.yml
like so:
stepFunctions:
stateMachines:
hello:
events:
- http:
path: posts/create
method: POST
response:
headers:
Content-Type: "'application/json'"
X-Application-Id: "'my-app'"
template:
application/json: |
{
"status": 200,
"info": "OK"
}
definition:
You can input an value as json in request body, the value is passed as the input value of your statemachine
$ curl -XPOST https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/posts/create -d '{"foo":"bar"}'
You can specify a list of API keys to be used by your service Rest API by adding an apiKeys array property to the provider object in serverless.yml. You'll also need to explicitly specify which endpoints are private and require one of the api keys to be included in the request by adding a private boolean property to the http event object you want to set as private. API Keys are created globally, so if you want to deploy your service to different stages make sure your API key contains a stage variable as defined below. When using API keys, you can optionally define usage plan quota and throttle, using usagePlan object.
Here's an example configuration for setting API keys for your service Rest API:
service: my-service
provider:
name: aws
apiKeys:
- myFirstKey
- ${opt:stage}-myFirstKey
- ${env:MY_API_KEY} # you can hide it in a serverless variable
usagePlan:
quota:
limit: 5000
offset: 2
period: MONTH
throttle:
burstLimit: 200
rateLimit: 100
functions:
hello:
handler: handler.hello
stepFunctions:
stateMachines:
statemachine1:
name: ${self:service}-${opt:stage}-statemachine1
events:
- http:
path: /hello
method: post
private: true
definition:
Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function"
StartAt: HelloWorld1
States:
HelloWorld1:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
End: true
plugins:
- serverless-step-functions
- serverless-pseudo-parameters
Please note that those are the API keys names, not the actual values. Once you deploy your service, the value of those API keys will be auto generated by AWS and printed on the screen for you to use. The values can be concealed from the output with the --conceal deploy option.
Clients connecting to this Rest API will then need to set any of these API keys values in the x-api-key header of their request. This is only necessary for functions where the private property is set to true.
The following config will attach a schedule event and causes the stateMachine crawl
to be called every 2 hours. The configuration allows you to attach multiple schedules to the same stateMachine. You can either use the rate
or cron
syntax. Take a look at the AWS schedule syntax documentation for more details.
stepFunctions:
stateMachines:
crawl:
events:
- schedule: rate(2 hours)
- schedule: cron(0 12 * * ? *)
definition:
Note: schedule
events are enabled by default.
This will create and attach a schedule event for the aggregate
stateMachine which is disabled. If enabled it will call the aggregate
stateMachine every 10 minutes.
stepFunctions:
stateMachines:
aggregate:
events:
- schedule:
rate: rate(10 minutes)
enabled: false
input:
key1: value1
key2: value2
stageParams:
stage: dev
- schedule:
rate: cron(0 12 * * ? *)
enabled: false
inputPath: '$.stageVariables'
Name and Description can be specified for a schedule event. These are not required properties.
events:
- schedule:
name: your-scheduled-rate-event-name
description: 'your scheduled rate event description'
rate: rate(2 hours)
By default, the plugin will create a new IAM role that allows AWS Events to start your state machine. Note that this role is different than the role assumed by the state machine. You can specify your own role instead (it must allow events.amazonaws.com
to assume it, and it must be able to run states:StartExecution
on your state machine):
events:
- schedule:
rate: rate(2 hours)
role: arn:aws:iam::xxxxxxxx:role/yourRole
You can specify input values to the Lambda function.
stepFunctions:
stateMachines:
stateMachineScheduled:
events:
- schedule:
rate: cron(30 12 ? * 1-5 *)
inputTransformer:
inputPathsMap:
time: '$.time'
stage: '$.stageVariables'
inputTemplate: '{"time": <time>, "stage" : <stage> }'
definition:
...
This will enable your Statemachine to be called by an EC2 event rule. Please check the page of Event Types for CloudWatch Events.
stepFunctions:
stateMachines:
first:
events:
- cloudwatchEvent:
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
definition:
...
You can alternatively use EventBridge:
stepFunctions:
stateMachines:
first:
events:
- eventBridge:
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
definition:
...
All the configurations in this section applies to both cloudwatchEvent
and eventBridge
.
Note: cloudwatchEvent
and eventBridge
events are enabled by default.
This will create and attach a disabled cloudwatchEvent
event for the myCloudWatch
statemachine.
stepFunctions:
stateMachines:
cloudwatchEvent:
events:
- cloudwatchEvent:
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
enabled: false
definition:
...
You can specify input values to the Lambda function.
stepFunctions:
stateMachines:
cloudwatchEvent:
events:
- cloudwatchEvent:
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
input:
key1: value1
key2: value2
stageParams:
stage: dev
- cloudwatchEvent:
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
inputPath: '$.stageVariables'
- cloudwatchEvent:
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
inputTransformer:
inputPathsMap:
stage: '$.stageVariables'
inputTemplate: '{ "stage": <stage> }'
definition:
...
You can also specify a CloudWatch Event description.
stepFunctions:
stateMachines:
cloudwatchEvent:
events:
- cloudwatchEvent:
description: 'CloudWatch Event triggered on EC2 Instance pending state'
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
definition:
...
You can also specify a CloudWatch Event name. Keep in mind that the name must begin with a letter; contain only ASCII letters, digits, and hyphens; and not end with a hyphen or contain two consecutive hyphens. More infomation here.
stepFunctions:
stateMachines:
cloudwatchEvent:
events:
- cloudwatchEvent:
name: 'my-cloudwatch-event-name'
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
definition:
...
You can also specify a CloudWatch Event RoleArn. The Amazon Resource Name (ARN) of the role that is used for target invocation.
Required: No
stepFunctions:
stateMachines:
cloudwatchEvent:
events:
- cloudwatchEvent:
name: 'my-cloudwatch-event-name'
iamRole: 'arn:aws:iam::012345678910:role/Events-InvokeStepFunctions-Role'
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
definition:
...
You can choose which CloudWatch Event bus:
stepFunctions:
stateMachines:
exampleCloudwatchEventStartsMachine:
events:
- cloudwatchEvent:
eventBusName: 'my-custom-event-bus'
event:
source:
- "my.custom.source"
detail-type:
- "My Event Type"
detail:
state:
- pending
definition:
...
You can choose which EventBridge Event bus:
stepFunctions:
stateMachines:
exampleEventBridgeEventStartsMachine:
events:
- eventBridge:
eventBusName: 'my-custom-event-bus'
event:
source:
- "my.custom.source"
detail-type:
- "My Event Type"
detail:
state:
- pending
definition:
...
You can configure a target queue to send dead-letter queue events to:
stepFunctions:
stateMachines:
exampleEventBridgeEventStartsMachine:
events:
- eventBridge:
eventBusName: 'my-custom-event-bus'
event:
source:
- "my.custom.source"
detail-type:
- "My Event Type"
detail:
state:
- pending
deadLetterConfig: 'arn:aws:sqs:us-east-1:012345678910:my-dlq' # SQS Arn
definition:
...
Important point
Don't forget to Grant permissions to the dead-letter queue, to do that you may need to have the ARN
of the generated EventBridge Rule
.
In order to get the ARN
you can use intrinsic functions against the logicalId
, this plugin generates logicalIds
following this format:
`${StateMachineName}EventsRuleCloudWatchEvent${index}`
Given this example 👇
stepFunctions:
stateMachines:
hellostepfunc1: # <---- StateMachineName
events:
- eventBridge:
eventBusName: 'my-custom-event-bus'
event:
source:
- "my.custom.source"
- eventBridge:
eventBusName: 'my-custom-event-bus'
event:
source:
- "my.custom.source"
deadLetterConfig: 'arn:aws:sqs:us-east-1:012345678910:my-dlq'
name: myStateMachine
definition:
Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function"
StartAt: HelloWorld1
States:
HelloWorld1:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
End: true
Then
# to get the Arn of the 1st EventBridge rule
!GetAtt Hellostepfunc1EventsRuleCloudWatchEvent1.Arn
# to get the Arn of the 2nd EventBridge rule
!GetAtt Hellostepfunc1EventsRuleCloudWatchEvent2.Arn
You can specify tags on each state machine. Additionally any global tags (specified under provider
section in your serverless.yml
) would be merged in as well.
If you don't want for global tags to be merged into your state machine, you can include the inheritGlobalTags
property for your state machine.
provider:
tags:
app: myApp
department: engineering
stepFunctions:
stateMachines:
hellostepfunc1:
name: myStateMachine
inheritGlobalTags: false
tags:
score: 42
definition: something
As a result, hellostepfunc1
will only have the tag of score: 42
, and not the tags at the provider level
Run sls deploy
, the defined Stepfunctions are deployed.
$ sls invoke stepf --name <stepfunctionname> --data '{"foo":"bar"}'
The IAM roles required to run Statemachine are automatically generated for each state machine in the serverless.yml
, with the IAM role name of StatesExecutionPolicy-<environment>
. These roles are tailored to the services that the state machine integrates with, for example with Lambda the InvokeFunction
is applied. You can also specify a custom ARN directly to the step functions lambda.
Here's an example:
stepFunctions:
stateMachines:
hello:
role: arn:aws:iam::xxxxxxxx:role/yourRole
definition:
It is also possible to use the CloudFormation intrinsic functions to reference resources from elsewhere. This allows for an IAM role to be created, and applied to the state machines all within the serverless file.
The below example shows the policy needed if your step function needs the ability to send a message to an sqs queue. To apply the role either the RoleName can be used as a reference in the state machine, or the role ARN can be used like in the example above. It is important to note that if you want to store your state machine role at a certain path, this must be specified on the Path
property on the new role.
stepFunctions:
stateMachines:
hello:
role:
Fn::GetAtt: ["StateMachineRole", "Arn"]
definition:
...
resources:
Resources:
StateMachineRole:
Type: AWS::IAM::Role
Properties:
RoleName: RoleName
Path: /path_of_state_machine_roles/
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- states.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: statePolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource:
- arn:aws:lambda:lambdaName
- Effect: Allow
Action:
- sqs:SendMessage
Resource:
- arn:aws:sqs::xxxxxxxx:queueName
The short form of the intrinsic functions (i.e. !Sub
, !Ref
) is not supported at the moment.
Here is serverless.yml sample to specify the stateMachine ARN to environment variables. This makes it possible to trigger your statemachine through Lambda events
functions:
hello:
handler: handler.hello
environment:
statemachine_arn: ${self:resources.Outputs.MyStateMachine.Value}
stepFunctions:
stateMachines:
hellostepfunc:
name: myStateMachine
definition:
<your definition>
resources:
Outputs:
MyStateMachine:
Description: The ARN of the example state machine
Value:
Ref: MyStateMachine
plugins:
- serverless-step-functions
When you have a large serverless project with lots of state machines your serverless.yml file can grow to a point where it is unmaintainable.
You can split step functions into external files and import them into your serverless.yml file.
There are two ways you can do this:
You can define the entire stateMachines
block in a separate file and import it in its entirety.
includes/state-machines.yml:
stateMachines:
hellostepfunc1:
name: myStateMachine1
definition:
<your definition>
hellostepfunc2:
name: myStateMachine2
definition:
<your definition>
serverless.yml:
stepFunctions:
${file(includes/state-machines.yml)}
plugins:
- serverless-step-functions
You can split up the stateMachines
block into separate files.
includes/state-machine-1.yml:
name: myStateMachine1
definition:
<your definition>
includes/state-machine-2.yml:
name: myStateMachine2
definition:
<your definition>
serverless.yml:
stepFunctions:
stateMachines:
hellostepfunc1:
${file(includes/state-machine-1.yml)}
hellostepfunc2:
${file(includes/state-machine-2.yml)}
plugins:
- serverless-step-functions
functions:
hello:
handler: handler.hello
stepFunctions:
stateMachines:
yourWateMachine:
definition:
Comment: "An example of the Amazon States Language using wait states"
StartAt: FirstState
States:
FirstState:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
Next: wait_using_seconds
wait_using_seconds:
Type: Wait
Seconds: 10
Next: wait_using_timestamp
wait_using_timestamp:
Type: Wait
Timestamp: '2015-09-04T01:59:00Z'
Next: wait_using_timestamp_path
wait_using_timestamp_path:
Type: Wait
TimestampPath: "$.expirydate"
Next: wait_using_seconds_path
wait_using_seconds_path:
Type: Wait
SecondsPath: "$.expiryseconds"
Next: FinalState
FinalState:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
End: true
plugins:
- serverless-step-functions
- serverless-pseudo-parameters
functions:
hello:
handler: handler.hello
stepFunctions:
stateMachines:
yourRetryMachine:
definition:
Comment: "A Retry example of the Amazon States Language using an AWS Lambda Function"
StartAt: HelloWorld
States:
HelloWorld:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
Retry:
- ErrorEquals:
- HandledError
IntervalSeconds: 1
MaxAttempts: 2
BackoffRate: 2
- ErrorEquals:
- States.TaskFailed
IntervalSeconds: 30
MaxAttempts: 2
BackoffRate: 2
- ErrorEquals:
- States.ALL
IntervalSeconds: 5
MaxAttempts: 5
BackoffRate: 2
End: true
plugins:
- serverless-step-functions
- serverless-pseudo-parameters
functions:
hello:
handler: handler.hello
stepFunctions:
stateMachines:
yourParallelMachine:
definition:
Comment: "An example of the Amazon States Language using a parallel state to execute two branches at the same time."
StartAt: Parallel
States:
Parallel:
Type: Parallel
Next: Final State
Branches:
- StartAt: Wait 20s
States:
Wait 20s:
Type: Wait
Seconds: 20
End: true
- StartAt: Pass
States:
Pass:
Type: Pass
Next: Wait 10s
Wait 10s:
Type: Wait
Seconds: 10
End: true
Final State:
Type: Pass
End: true
plugins:
- serverless-step-functions
- serverless-pseudo-parameters
functions:
hello:
handler: handler.hello
stepFunctions:
stateMachines:
yourCatchMachine:
definition:
Comment: "A Catch example of the Amazon States Language using an AWS Lambda Function"
StartAt: HelloWorld
States:
HelloWorld:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
Catch:
- ErrorEquals: ["HandledError"]
Next: CustomErrorFallback
- ErrorEquals: ["States.TaskFailed"]
Next: ReservedTypeFallback
- ErrorEquals: ["States.ALL"]
Next: CatchAllFallback
End: true
CustomErrorFallback:
Type: Pass
Result: "This is a fallback from a custom lambda function exception"
End: true
ReservedTypeFallback:
Type: Pass
Result: "This is a fallback from a reserved error code"
End: true
CatchAllFallback:
Type: Pass
Result: "This is a fallback from a reserved error code"
End: true
plugins:
- serverless-step-functions
- serverless-pseudo-parameters
functions:
hello1:
handler: handler.hello1
hello2:
handler: handler.hello2
hello3:
handler: handler.hello3
hello4:
handler: handler.hello4
stepFunctions:
stateMachines:
yourChoiceMachine:
definition:
Comment: "An example of the Amazon States Language using a choice state."
StartAt: FirstState
States:
FirstState:
Type: Task
Resource:
Fn::GetAtt: [hello, Arn]
Next: ChoiceState
ChoiceState:
Type: Choice
Choices:
- Variable: "$.foo"
NumericEquals: 1
Next: FirstMatchState
- Variable: "$.foo"
NumericEquals: 2
Next: SecondMatchState
Default: DefaultState
FirstMatchState:
Type: Task
Resource:
Fn::GetAtt: [hello2, Arn]
Next: NextState
SecondMatchState:
Type: Task
Resource:
Fn::GetAtt: [hello3, Arn]
Next: NextState
DefaultState:
Type: Fail
Cause: "No Matches!"
NextState:
Type: Task
Resource:
Fn::GetAtt: [hello4, Arn]
End: true
plugins:
- serverless-step-functions
- serverless-pseudo-parameters
functions:
entry:
handler: handler.entry
mapTask:
handler: handler.mapTask
stepFunctions:
stateMachines:
yourMapMachine:
definition:
Comment: "A Map example of the Amazon States Language using an AWS Lambda Function"
StartAt: FirstState
States:
FirstState:
Type: Task
Resource:
Fn::GetAtt: [entry, Arn]
Next: mapped_task
mapped_task:
Type: Map
Iterator:
StartAt: FirstMapTask
States:
FirstMapTask:
Type: Task
Resource:
Fn::GetAtt: [mapTask, Arn]
End: true
End: true
plugins:
- serverless-step-functions
Author: serverless-operations
Source Code: https://github.com/serverless-operations/serverless-step-functions
License: View license
1619508060
In this article, we’ll discuss information about the Step Finance project and STEP token
Step Finance is a portfolio visualisation platform which aggregates all LPs, tokens, farms and positions that a user may have associated with their wallet and displays it in an easy to use dashboard with various useful metrics and visualisations. Step aims to be the page which DeFi users have open all day with all the functions and information they need to make informed decisions.
Step will also enable users to interact directly with their favourite protocols from within the dashboard which could include farming, pools, swaps and automated strategies. Many of these value added services will be done for a fee which will be collected and used to buyback and distribute tokens to STEP tokenholders. Today we will discuss the tokenomics behind STEP token.
Step must connect to the relevant Programs (smart contracts) built on Solana and query them to be able to get the information necessary to display to a user and execute transactions. Step’s core functions that are implemented or planned are:
Wallet balances:
Dashboard view of all tokens you have associated with an individual address with options to consolidate multiple wallet addresses into the one view and display value over time denominated in a selection of base currencies.
LP tokens view:
Get information related to current LP tokens you hold in Solana AMMs and visualise performance of the pools over time, vs base currency/hodling, fee revenue, impermanent loss, yield and APYs.
Yield Farming and Staking:
Step will provide a table of current yield farming opportunities on Solana with easy one-click entry into any of the farms. Enter, exit and claim yield farm rewards on this page. This page also handles single asset staking on Solana validator nodes.
DEX positions:
The base AMM from which all DEXes on Solana are using or forked from is Serum. This is a full orderbook DEX where users can buy and sell any supported token. Step will display current wallet balances, open orders, unsettled balances and any other related information to user positions in Serum.
Investment Strategies:
Step was founded in the February 2021 Solana Defi Hackathon as an automated strategy platform. This page will cover automated strategies like DCA, EMAs and RSIs and others which users can enter into with potential in future to enable users to make their own custom strategies.
Swap aggregation:
All Programs on Solana are composable and as such liquidity can be fragmented among various pools and AMM programs. It is therefore important to be able to ensure users get the best possible deal on their swaps and Step being a DeFi aggregator is well placed to offer swap aggregation from within Step.
Design is crucial to STEP being the front page of the Solana ecosystem. The STEP team aims for Step.finance to be a page which is used everyday by everyone in the Solana ecosystem. Step will be something which will have coverage of all other projects on the Step dashboard, providing an easy to use ‘Wallet view’ for all the coins, LPs, farms and positions a user may have associated with their wallet.
Design Principles:
It is essential to the success of the project that STEP actually generates revenue and adds value to the token. Therefore there are several value accrual mechanisms for the token itself which will further generate positive interest and incentives in a positive feedback loop. The below is a list of value accrual mechanics that the team intend to implement on the Step platform:
Transaction fees: The most basic and simple value accrual is STEP taking a fee on certain interactions on the STEP platform (swaps, yield farms, automated strategies, bridges etc). The majority of this fee revenue goes to stakers of the STEP token (80%) with the remaining 20% sent to the treasury.
Buyback and distribute: The core value accrual mechanic of STEP is buyback of STEP tokens from revenue generated by transaction fees in the protocol which are then distributed to stakers. This gives a native APY for staking the STEP token and aligns incentives of tokenholders and the team with the success of the STEP platform. This is awarded proportionally to the liquidity an LP stakes in this pool.
Reducing supply emission rate: Supply emissions should reduce over time without any large cliffs (like evident with SWRV which did a 90% cliff in emissions that made their TVL fall from $700m to $3m and cause people to forget/leave the project). The optimal scenario is a gradual reduction in new emissions over time at a set rate. For STEP this emission reduction will be 4% weekly of the community and ecosystem reserve. With a steady or increasing demand and a falling emission rate there reaches an inflection point where buybacks volume is greater than new emissions creating NET positive buy pressure on the STEP token.
Aggregation: A core part of the Step Finance platform is being an aggregator for various user actions like AMM Swaps and Yield Farms. Step will implement a swap router on Solana for improved liquidity and price discovery in AMM swap pools and also offer a number of tools for yield farmers like single asset entry/exit into LPs, autocompounding yields and managed yield farm vaults. Fees collected for these services will flow back to STEP stakers.
Lockups: Incentivising users to take coins off the market is one way to reduce potential sell pressure and ensure given flat or growing demand that there is NET positive buy pressure. STEP intends to implement various lockup LP pools where tokens will be locked for a specified amount of time and be able to share in a higher rate of emissions or staking revenue compared with staking pools which allow immediate withdrawal.
Automated Trading Strategies: Step started as an automated strategy platform and our intent has always been to build out this functionality. There are various automated vaults and trading strategy contracts on Solana which STEP will be exposing to users on the Step dashboard. Step will charge a fee to enter and exit these vaults and potentially for some strategies a performance fee as well. These fees will accrue to STEP stakers.
Stake for Access: There are a number of investment indicators, analytics, portfolio insights which Step plans to make available to users who are stakers of STEP. Stake-for-access is a superior model to subscriptions or paid plans and adds additional value to STEP token holders.
Fiat-Defi integrations: Step intends to implement arms length widgets which can handle fiat on/off ramps into Solana via Step. This brings more money into the Step ecosystem, puts it to productive use in Step strategies and swaps and ensure Step remains a focal point for access into and out of the Solana ecosystem. Step has a fiat partner onboard for this once we are ready for development.
Referrals: Referrals are a growing value additive mechanism in the crypto world with DeFi projects such as 1INCH utilising it effectively. One user may refer others via a weblink and earn a share of the fees. Step intends to implement this system for Swaps and potentially other automated strategies.
Cross chain Integration: Being multi-chain capable is a core principle at STEP finance, there are several bridges under development on Solana, we will support all of them and likely take a small fee on each tx. Adding additional blockchains is a path towards more TVL, users, revenue streams for STEP…
Governance:
Step.finance has a core team of 3 who initially will guide the direction of the project. There will be no governance functions initially but the team is open to exploring further tokenholder involvement in the decision making process once STEP token is established.
The STEP token ensures there is a clear alignment of incentives between users, tokenholders and the team so that Step can continue to grow in a sustainable and productive manner for the benefit of all DeFi users. It is therefore crucial that the $STEP token have strong fundamental value accrual structures in place which build value over time.
Overview:
Max Supply 1 000 000 000 STEP
Emission Period: 2 years
Weekly Emission Reduction: -4%
Airdrops % of Supply: 1%
Founders % of Supply: 20%
Founder Lockup: Lockup over 2 years vested in 25% increments every 6 months.
Treasury % of Supply: 12.219%
Presale Investors: 11.780%
Presale Lockup: 2 years vesting. 50% available after 1 year, 50% available year 2.
Community & Ecosystem % of Supply: 55% (LPs and Trade executors)
Token Type: SPL (Solana Native)
Principles:
Staking and Buybacks:
STEP has been listed on a number of crypto exchanges, unlike other main cryptocurrencies, it cannot be directly purchased with fiats money. However, You can still easily buy this coin by first buying Bitcoin, ETH, USDT, BNB from any large exchanges and then transfer to the exchange that offers to trade this coin, in this guide article we will walk you through in detail the steps to buy STEP token.
You will have to first buy one of the major cryptocurrencies, usually either Bitcoin (BTC), Ethereum (ETH), Tether (USDT), Binance (BNB)…
We will use Binance Exchange here as it is one of the largest crypto exchanges that accept fiat deposits.
Binance is a popular cryptocurrency exchange which was started in China but then moved their headquarters to the crypto-friendly Island of Malta in the EU. Binance is popular for its crypto to crypto exchange services. Binance exploded onto the scene in the mania of 2017 and has since gone on to become the top crypto exchange in the world.
Once you finished the KYC process. You will be asked to add a payment method. Here you can either choose to provide a credit/debit card or use a bank transfer, and buy one of the major cryptocurrencies, usually either Bitcoin (BTC), Ethereum (ETH), Tether (USDT), Binance (BNB)…
Step by Step Guide : What is Binance | How to Create an account on Binance (Updated 2021)
Next step - Transfer your cryptos to an Altcoin Exchange
Since STEP is an altcoin we need to transfer our coins to an exchange that STEP can be traded. Below is a list of exchanges that offers to trade STEP in various market pairs, head to their websites and register for an account.
Once finished you will then need to make a BTC/ETH/USDT/BNB deposit to the exchange from Binance depending on the available market pairs. After the deposit is confirmed you may then purchase STEP from the exchange: https://dex.projectserum.com
The top exchange for trading in STEP token is currently …
There are a few popular crypto exchanges where they have decent daily trading volumes and a huge user base. This will ensure you will be able to sell your coins at any time and the fees will usually be lower. It is suggested that you also register on these exchanges since once STEP gets listed there it will attract a large amount of trading volumes from the users there, that means you will be having some great trading opportunities!
Top exchanges for token-coin trading. Follow instructions and make unlimited money
☞ https://www.binance.com
☞ https://www.bittrex.com
☞ https://www.poloniex.com
☞ https://www.bitfinex.com
☞ https://www.huobi.com
Find more information STEP
☞ Website ☞ Explorer ☞ Source Code ☞ Social Channel ☞ Social Channel 2 ☞ Coinmarketcap
🔺DISCLAIMER: The Information in the post isn’t financial advice, is intended FOR GENERAL INFORMATION PURPOSES ONLY. Trading Cryptocurrency is VERY risky. Make sure you understand these risks and that you are responsible for what you do with your money.
🔥 If you’re a beginner. I believe the article below will be useful to you
⭐ ⭐ ⭐ What You Should Know Before Investing in Cryptocurrency - For Beginner ⭐ ⭐ ⭐
I hope this post will help you. Don’t forget to leave a like, comment and sharing it with others. Thank you!
#bitcoin #cryptocurrency #step #step finance