├── LICENSE
├── README.md
├── demo.gif
└── src
├── Cells
├── Counter
│ ├── CounterCell.php
│ └── counter.php
├── CounterSigned
│ ├── CounterSignedCell.php
│ └── counter_signed.php
├── TableAdvanced
│ ├── TableAdvancedCell.php
│ └── table_advanced.php
└── TableSimple
│ ├── TableSimpleCell.php
│ └── table_simple.php
├── Config
├── Registrar.php
└── Routes.php
├── Controllers
├── Books.php
├── Paragraphs.php
└── Tasks.php
├── Database
├── Migrations
│ ├── .gitkeep
│ ├── 2022-10-01-080730_AddBooksTable.php
│ ├── 2022-12-04-110940_AddTasksTable.php
│ └── 2022-12-12-101738_AddParagraphsTable.php
└── Seeds
│ ├── .gitkeep
│ ├── SeedBooksTable.php
│ ├── SeedDemo.php
│ └── SeedParagraphsTable.php
├── Helpers
└── alert_helper.php
├── Models
├── .gitkeep
├── BookModel.php
├── ParagraphModel.php
└── TaskModel.php
├── TableHelper.php
└── Views
├── Pager
└── default_htmx_full.php
├── books
├── index.php
├── table.php
├── table_row.php
├── table_row_add.php
└── table_row_edit.php
├── cells
└── index.php
├── home.php
├── layout.php
├── paragraphs
├── edit.php
└── index.php
└── tasks
├── index.php
├── task.php
└── tasks_summary.php
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Michal Sniatala
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CodeIgniter HTMX Demo
2 |
3 | This is a demo for [CodeIgniter HTMX](https://github.com/michalsn/codeigniter-htmx) helper library. Demo requires:
4 |
5 | - CodeIgniter HTMX library installed via composer or manually,
6 | - CodeIgniter 4.3 or later version.
7 |
8 | 
9 |
10 | #### Available demos:
11 |
12 | - **Books** - searching, pagination, inline edit
13 | - **Tasks** - events
14 | - **Paragraphs** - sorting, modal edit
15 | - **Controlled Cells** - widget like components, signed-urls (the last one requires [CodeIgniter Signed-URL](https://github.com/michalsn/codeigniter-signed-url) library)
16 |
17 | ## Installation
18 |
19 | 1. Install [CodeIgniter 4](https://codeigniter.com/) with composer (command below) or [manually](https://codeigniter.com/user_guide/installation/installing_manual.html).
20 | ```console
21 | composer create-project codeigniter4/appstarter codeigniterhtmx
22 | cd codeigniterhtmx
23 | ```
24 | 2. Install [CodeIgniter HTMX](https://github.com/michalsn/codeigniter-htmx) library with composer (command below) or [manually](https://michalsn.github.io/codeigniter-htmx/installation/#manual-installation).
25 | ```console
26 | composer require michalsn/codeigniter-htmx
27 | ```
28 | 3. Manually download ZIP file of this project and place it in the desired folder.
29 |
30 | - The example will assume that it will be `codeigniterhtmx/app/ThirdParty/htmx-demo`.
31 | - Then enable it by editing the `codeigniterhtmx/app/Config/Autoload.php` file and adding the `Michalsn\CodeIgniterHtmxDemo` namespace to the `$psr4` array, like in the below example:
32 |
33 | ```php
34 | APPPATH, // For custom app namespace
40 | 'Config' => APPPATH . 'Config',
41 | 'Michalsn\CodeIgniterHtmxDemo' => APPPATH . 'ThirdParty/htmx-demo/src',
42 | ];
43 |
44 | // ...
45 | ```
46 |
47 | ## Database
48 |
49 | First, make sure to create a database for the project. You can name it `codeigniterhtmx` and set credentials for the database connection in `codeigniterhtmx/app/Config/Database.php`, see [framework's user guide](https://codeigniter.com/user_guide/database/configuration.html) for more information.
50 |
51 | Before running the examples you have to migrate the database. Make sure you have set correct credentials to the database and then run the command:
52 | ```console
53 | php spark migrate --all
54 | ```
55 |
56 | And preferably run the seeds:
57 |
58 | **For Unix:**
59 | ```console
60 | php spark db:seed Michalsn\\CodeIgniterHtmxDemo\\Database\\Seeds\\SeedDemo
61 | ```
62 |
63 | **For Windows:**
64 | ```console
65 | php spark db:seed Michalsn\CodeIgniterHtmxDemo\Database\Seeds\SeedDemo
66 | ```
67 |
68 | ## Running the demo
69 |
70 | The default route to the demo page is `demo`.
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalsn/codeigniter-htmx-demo/99507f0de424a2148575caeb89e59099c85b9e02/demo.gif
--------------------------------------------------------------------------------
/src/Cells/Counter/CounterCell.php:
--------------------------------------------------------------------------------
1 | count++;
19 | return $this->render();
20 | }
21 |
22 | /**
23 | * Decrement
24 | *
25 | * @return void
26 | */
27 | public function decrement()
28 | {
29 | $this->count--;
30 | return $this->render();
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/src/Cells/Counter/counter.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
15 | +
16 |
17 |
21 | -
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Cells/CounterSigned/CounterSignedCell.php:
--------------------------------------------------------------------------------
1 | getGet() !== []) {
17 | try {
18 | service('signedurl')->verify(service('incomingrequest'));
19 | } catch (SignedUrlException $e) {
20 | throw $e;
21 | }
22 | }
23 | }
24 |
25 | /**
26 | * Increment
27 | *
28 | * @return void
29 | */
30 | public function increment()
31 | {
32 | $this->count += $this->step;
33 | return $this->render();
34 | }
35 |
36 | /**
37 | * Decrement
38 | *
39 | * @return void
40 | */
41 | public function decrement()
42 | {
43 | $this->count -= $this->step;
44 | return $this->render();
45 | }
46 |
47 | /**
48 | * Get query string
49 | *
50 | * @return string;
51 | */
52 | public function getQueryString()
53 | {
54 | return http_build_query([
55 | 'count' => $this->count,
56 | 'step' => $this->step,
57 | ]);
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/src/Cells/CounterSigned/counter_signed.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
15 | +
16 |
17 |
21 | -
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Cells/TableAdvanced/TableAdvancedCell.php:
--------------------------------------------------------------------------------
1 | 2, 5 => 5, 10 => 10];
22 |
23 | protected string $baseURL = 'cells/table-advanced';
24 |
25 | protected array $books;
26 | protected Pager $pager;
27 |
28 | public function mount()
29 | {
30 | if (! in_array($this->sortColumn, $this->validSortColumns)) {
31 | throw new InvalidArgumentException('Sort column is out of the range.');
32 | }
33 |
34 | if (! in_array($this->sortDirection, $this->validSortDirections)) {
35 | throw new InvalidArgumentException('Sort direction is out of the range.');
36 | }
37 |
38 | if (! in_array($this->limit, array_keys($this->perPage))) {
39 | throw new InvalidArgumentException('Items per page is out of the range.');
40 | }
41 |
42 | helper('form');
43 |
44 | $model = model(BookModel::class);
45 |
46 | $this->books = $model
47 | ->when($this->search !== '', function ($query) {
48 | return $query
49 | ->like('title', $this->search, 'both')
50 | ->orLike('author', $this->search, 'both');
51 | })
52 | ->orderBy($this->sortColumn, $this->sortDirection)
53 | ->paginate($this->limit, 'default', $this->page);
54 |
55 | $this->pager = $model->pager->setPath($this->baseURL);
56 | }
57 |
58 | protected function getBooksProperty(): array
59 | {
60 | return $this->books;
61 | }
62 |
63 | protected function getPagerProperty(): Pager
64 | {
65 | return $this->pager;
66 | }
67 |
68 | protected function getPerPageProperty(): array
69 | {
70 | return $this->perPage;
71 | }
72 |
73 | protected function baseURL(): string
74 | {
75 | $queryString = [
76 | 'sortColumn' => $this->sortColumn,
77 | 'sortDirection' => $this->sortDirection,
78 | ];
79 |
80 | return $this->baseURL . '?' . http_build_query($queryString);
81 | }
82 |
83 | protected function sortByURL(string $column): string
84 | {
85 | if (! in_array($column, $this->validSortColumns)) {
86 | throw new InvalidArgumentException('Sort column is out of the range.');
87 | }
88 |
89 | $queryString = [
90 | 'sortColumn' => $column,
91 | 'sortDirection' => $this->sortColumn === $column && $this->sortDirection === 'asc' ? 'desc' : 'asc',
92 | ];
93 |
94 | return $this->baseURL . '?' . http_build_query($queryString);
95 | }
96 |
97 | protected function getSortIndicator(string $column): string
98 | {
99 | if (! in_array($column, $this->validSortColumns)) {
100 | throw new InvalidArgumentException('Sort column is out of the range.');
101 | }
102 |
103 | if ($column === $this->sortColumn) {
104 | return $this->sortDirection === 'asc' ? '↑' : '↓';
105 | }
106 |
107 | return '';
108 | }
109 | }
--------------------------------------------------------------------------------
/src/Cells/TableAdvanced/table_advanced.php:
--------------------------------------------------------------------------------
1 |
2 |
31 |
32 |
33 |
34 |
No books
35 |
36 | No books found :(
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | = anchor($this->sortByURL('id'), 'ID', ['hx-get' => site_url($this->sortByURL('id'))]); ?> = $this->getSortIndicator('id'); ?>
45 | = anchor($this->sortByURL('title'), 'Title', ['hx-get' => site_url($this->sortByURL('title'))]); ?> = $this->getSortIndicator('title'); ?>
46 | = anchor($this->sortByURL('author'), 'Author', ['hx-get' => site_url($this->sortByURL('author'))]); ?> = $this->getSortIndicator('author'); ?>
47 |
48 |
49 |
50 |
51 |
52 | = $book->id; ?>
53 | = esc($book->title); ?>
54 | = esc($book->author); ?>
55 |
56 |
57 |
58 |
59 |
60 |
61 | = $pager->links('default', 'default_htmx_full'); ?>
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/Cells/TableSimple/TableSimpleCell.php:
--------------------------------------------------------------------------------
1 | books = $model
25 | ->paginate(2, 'default', $this->page);
26 |
27 | $this->pager = $model->pager->setPath($this->baseURL);
28 | }
29 |
30 | protected function getBooksProperty(): array
31 | {
32 | return $this->books;
33 | }
34 |
35 | protected function getPagerProperty(): Pager
36 | {
37 | return $this->pager;
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Cells/TableSimple/table_simple.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
No books
11 |
12 | No books found :(
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ID
21 | Title
22 | Author
23 |
24 |
25 |
26 |
27 |
28 | = $book->id; ?>
29 | = esc($book->title); ?>
30 | = esc($book->author); ?>
31 |
32 |
33 |
34 |
35 |
36 |
37 | = $pager->links('default', 'default_htmx_full'); ?>
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/Config/Registrar.php:
--------------------------------------------------------------------------------
1 | [
11 | 'default_htmx_full' => 'Michalsn\CodeIgniterHtmxDemo\Views\Pager\default_htmx_full',
12 | ],
13 | ];
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Config/Routes.php:
--------------------------------------------------------------------------------
1 | get('demo', static function () {
6 | return view('Michalsn\CodeIgniterHtmxDemo\Views\home');
7 | });
8 |
9 | $routes->group('books', ['namespace' => 'Michalsn\CodeIgniterHtmxDemo\Controllers'], static function ($routes) {
10 | $routes->get('/', 'Books::index');
11 | $routes->get('table', 'Books::table');
12 | $routes->get('show/(:num)', 'Books::show/$1');
13 | $routes->delete('delete/(:num)', 'Books::delete/$1');
14 | $routes->match(['GET', 'POST'], 'edit/(:num)', 'Books::edit/$1');
15 | $routes->match(['GET', 'POST'], 'add', 'Books::add');
16 | });
17 |
18 | $routes->group('tasks', ['namespace' => 'Michalsn\CodeIgniterHtmxDemo\Controllers'], static function ($routes) {
19 | $routes->get('/', 'Tasks::index');
20 | $routes->get('(active|completed)', 'Tasks::index/$1');
21 | $routes->post('/', 'Tasks::add');
22 | $routes->put('toggle/(:num)', 'Tasks::toggle/$1');
23 | $routes->put('toggle-all', 'Tasks::toggleAll');
24 | $routes->delete('(:num)', 'Tasks::delete/$1');
25 | $routes->delete('clear-completed', 'Tasks::clearCompleted');
26 | $routes->get('summary', 'Tasks::summary');
27 | });
28 |
29 | $routes->group('paragraphs', ['namespace' => 'Michalsn\CodeIgniterHtmxDemo\Controllers'], static function ($routes) {
30 | $routes->get('/', 'Paragraphs::index');
31 | $routes->match(['GET', 'POST'], 'edit/(:num)', 'Paragraphs::edit/$1');
32 | $routes->post('reorder', 'Paragraphs::reorder');
33 | });
34 |
35 | $routes->group('cells', static function ($routes) {
36 | $routes->get('/', static function () {
37 | return view('Michalsn\CodeIgniterHtmxDemo\Views\cells\index');
38 | });
39 |
40 | $routes->group('counter', static function ($routes) {
41 | $routes->get('increment', static function () {
42 | return view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\Counter\CounterCell::increment', service('request')->getGet());
43 | });
44 |
45 | $routes->get('decrement', static function () {
46 | return view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\Counter\CounterCell::decrement', service('request')->getGet());
47 | });
48 | });
49 |
50 | $routes->group('counter-signed', static function ($routes) {
51 | $routes->get('increment', static function () {
52 | return view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\CounterSigned\CounterSignedCell::increment', service('request')->getGet());
53 | });
54 |
55 | $routes->get('decrement', static function () {
56 | return view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\CounterSigned\CounterSignedCell::decrement', service('request')->getGet());
57 | });
58 | });
59 |
60 | $routes->get('table-simple', static function () {
61 | return view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\TableSimple\TableSimpleCell', service('request')->getGet());
62 | });
63 |
64 | $routes->get('table-advanced', static function () {
65 | return view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\TableAdvanced\TableAdvancedCell', service('request')->getGet());
66 | });
67 | });
68 |
69 |
--------------------------------------------------------------------------------
/src/Controllers/Books.php:
--------------------------------------------------------------------------------
1 | $this->request->getGet('limit') ?? 5,
25 | 'page' => $this->request->getGet('page') ?? 1,
26 | 'search' => $this->request->getGet('search') ?? '',
27 | 'sortColumn' => $this->request->getGet('sortColumn') ?? 'id',
28 | 'sortDirection' => $this->request->getGet('sortDirection') ?? 'asc',
29 | ];
30 |
31 | $rules = [
32 | 'limit' => ['is_natural_no_zero', 'less_than_equal_to[10]'],
33 | 'page' => ['is_natural', 'greater_than_equal_to[1]'],
34 | 'search' => ['string'],
35 | 'sortColumn' => ['in_list[id,title,author]'],
36 | 'sortDirection' => ['in_list[asc,desc]'],
37 | ];
38 |
39 | if (! $this->validateData($data, $rules)) {
40 | throw new InvalidArgumentException(implode(PHP_EOL, $this->validator->getErrors()));
41 | }
42 |
43 | helper('form');
44 |
45 | $model = model(BookModel::class);
46 |
47 | $data['books'] = $model
48 | ->when($data['search'] !== '', function ($query) use ($data) {
49 | return $query
50 | ->like('title', $data['search'], 'both')
51 | ->orLike('author', $data['search'], 'both');
52 | })
53 | ->orderBy($data['sortColumn'], $data['sortDirection'])
54 | ->paginate((int) $data['limit'], 'default', (int) $data['page']);
55 |
56 | $data['pager'] = $model->pager->setPath($this->baseURL);
57 | $data['table'] = new TableHelper($this->baseURL, $data['sortColumn'], $data['sortDirection']);
58 |
59 | if ($this->request->isHtmx() && ! $this->request->isBoosted()) {
60 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\table', $data);
61 | }
62 |
63 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\index', $data);
64 | }
65 |
66 | /**
67 | * Edit row.
68 | */
69 | public function edit(int $id): string
70 | {
71 | $model = model(BookModel::class);
72 |
73 | if (! $book = $model->find($id)) {
74 | throw new PageNotFoundException('Incorrect book id.');
75 | }
76 |
77 | helper(['form', 'alert']);
78 |
79 | $validation = service('validation');
80 |
81 | if ($this->request->getMethod() !== 'post') {
82 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\table_row_edit', [
83 | 'book' => $book, 'validation' => $validation,
84 | ]);
85 | }
86 |
87 | $post = $this->request->getPost(['title', 'author']);
88 |
89 | $validation->setRules([
90 | 'title' => ['required', 'string', 'min_length[2]', 'max_length[100]'],
91 | 'author' => ['required', 'string', 'min_length[5]', 'max_length[100]'],
92 | ]);
93 |
94 | if (! $validation->run($post)) {
95 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\table_row_edit', [
96 | 'book' => $book, 'validation' => $validation,
97 | ]).alert('danger', 'Form validation failed.');
98 | }
99 |
100 | $model->update($book->id, $post);
101 |
102 | $book = (object) array_merge((array) $book, $post);
103 |
104 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\table_row', [
105 | 'book' => $book,
106 | ]).alert('success', 'Book was updated.');
107 | }
108 |
109 | /**
110 | * Add row.
111 | */
112 | public function add(): string
113 | {
114 | $model = model(BookModel::class);
115 |
116 | helper(['form', 'alert']);
117 |
118 | $validation = service('validation');
119 |
120 | if ($this->request->getMethod() !== 'post') {
121 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\table_row_add', [
122 | 'validation' => $validation,
123 | ]);
124 | }
125 |
126 | $post = $this->request->getPost(['title', 'author']);
127 |
128 | $validation->setRules([
129 | 'title' => ['required', 'string', 'min_length[2]', 'max_length[100]'],
130 | 'author' => ['required', 'string', 'min_length[5]', 'max_length[100]'],
131 | ]);
132 |
133 | if (! $validation->run($post)) {
134 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\table_row_add', [
135 | 'validation' => $validation,
136 | ]).alert('danger', 'Form validation failed.');
137 | }
138 |
139 | if ($id = $model->insert($post)) {
140 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\table_row', [
141 | 'book' => $model->find($id),
142 | ]).alert('success', 'The book was added successfully.');
143 | }
144 |
145 | return alert('danger', 'Adding a book failed.');
146 | }
147 |
148 | /**
149 | * Show row.
150 | */
151 | public function show(int $id): string
152 | {
153 | if (! $book = model(BookModel::class)->find($id)) {
154 | throw new PageNotFoundException('Incorrect book ID.');
155 | }
156 |
157 | return view('Michalsn\CodeIgniterHtmxDemo\Views\books\table_row', [
158 | 'book' => $book, 'validation' => service('validation'),
159 | ]);
160 | }
161 |
162 | /**
163 | * Delete row.
164 | */
165 | public function delete(int $id): string
166 | {
167 | helper('alert');
168 |
169 | if (model(BookModel::class)->delete($id)) {
170 | return alert('success', 'The book has been deleted.');
171 | }
172 |
173 | return alert('danger', 'Deleting the book failed, or the book does not exist.');
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/Controllers/Paragraphs.php:
--------------------------------------------------------------------------------
1 | $model->orderBy('sort', 'asc')->findAll(),
22 | ];
23 |
24 | if ($this->request->isHtmx() && ! $this->request->isBoosted()) {
25 | return view_fragment('Michalsn\CodeIgniterHtmxDemo\Views\paragraphs\index', 'paragraphs', $data);
26 | }
27 |
28 | return view('Michalsn\CodeIgniterHtmxDemo\Views\paragraphs\index', $data);
29 | }
30 |
31 | /**
32 | * Edit paragraph
33 | */
34 | public function edit(int $id): string
35 | {
36 | $model = model(ParagraphModel::class);
37 |
38 | if (! $paragraph = $model->find($id)) {
39 | throw new PageNotFoundException('Incorrect paragraph id.');
40 | }
41 |
42 | helper(['form', 'alert']);
43 |
44 | $validation = service('validation');
45 |
46 | if ($this->request->getMethod() !== 'post') {
47 | return view('Michalsn\CodeIgniterHtmxDemo\Views\paragraphs\edit', [
48 | 'paragraph' => $paragraph, 'validation' => $validation,
49 | ]);
50 | }
51 |
52 | $post = $this->request->getPost(['title', 'body']);
53 |
54 | $validation->setRules([
55 | 'title' => ['required', 'string', 'min_length[5]', 'max_length[64]'],
56 | 'body' => ['required', 'string', 'min_length[20]', 'max_length[255]'],
57 | ]);
58 |
59 | if (! $validation->run($post)) {
60 | $this->response->setReswap('innerHTML')->setRetarget('#modal-fields');
61 | return view_fragment('Michalsn\CodeIgniterHtmxDemo\Views\paragraphs\edit', 'fields', [
62 | 'paragraph' => $paragraph, 'validation' => $validation,
63 | ]).alert('danger', 'Form validation failed.');
64 | }
65 |
66 | $model->update($paragraph->id, $post);
67 |
68 | $this->response->triggerClientEvent('closeModal');
69 |
70 | return $this->index().alert('success', 'Paragraph was updated.');
71 | }
72 |
73 | /**
74 | * Reorder paragraphs
75 | *
76 | * @throws ReflectionException
77 | */
78 | public function reorder(): string
79 | {
80 | $ids = array_map('intval', $this->request->getPost('ids') ?? []);
81 |
82 | if (empty($ids)) {
83 | throw new PageNotFoundException('Missing paragraph IDs.');
84 | }
85 |
86 | helper('alert');
87 |
88 | $model = model(ParagraphModel::class);
89 |
90 | $count = $model->whereIn('id', $ids)->countAllResults();
91 |
92 | if ($count !== count($ids)) {
93 | return alert('danger', 'Incorrect number of paragraphs.');
94 | }
95 |
96 | $data = [];
97 |
98 | foreach ($ids as $key => $id) {
99 | $data[] = [
100 | 'id' => $id,
101 | 'sort' => $key + 1
102 | ];
103 | }
104 |
105 | $model->updateBatch($data, 'id');
106 |
107 | return $this->index().alert('success', 'The order of paragraphs has been changed.');
108 | }
109 | }
--------------------------------------------------------------------------------
/src/Controllers/Tasks.php:
--------------------------------------------------------------------------------
1 | $type,
20 | 'tasks' => $model->getAll($type),
21 | 'countActive' => $model->countByType('active'),
22 | 'countCompleted' => $model->countByType('completed'),
23 | ];
24 |
25 | if ($this->request->isHtmx() && ! $this->request->isBoosted()) {
26 | return view_fragment('Michalsn\CodeIgniterHtmxDemo\Views\tasks\index', 'tasks', $data);
27 | }
28 |
29 | return view('Michalsn\CodeIgniterHtmxDemo\Views\tasks\index', $data);
30 | }
31 |
32 | /**
33 | * Add task.
34 | */
35 | public function add(): string
36 | {
37 | $model = model(TaskModel::class);
38 |
39 | helper(['form', 'alert']);
40 |
41 | $validation = service('validation');
42 |
43 | $post = $this->request->getPost(['name']);
44 |
45 | $validation->setRules([
46 | 'name' => ['required', 'string', 'min_length[5]', 'max_length[64]'],
47 | ]);
48 |
49 | if (! $validation->run($post)) {
50 | return alert('danger', $validation->getError('name'));
51 | }
52 |
53 | if ($id = $model->insert($post)) {
54 |
55 | $this->response->triggerClientEvent('taskAdded');
56 |
57 | return view('Michalsn\CodeIgniterHtmxDemo\Views\tasks\task', [
58 | 'task' => $model->find($id),
59 | ]).alert('success', 'New task was added successfully.');
60 | }
61 |
62 | return alert('danger', 'Adding a task failed.');
63 | }
64 |
65 | /**
66 | * Toggle task.
67 | */
68 | public function toggle(int $id)
69 | {
70 | helper('alert');
71 |
72 | $model = model(TaskModel::class);
73 |
74 | if (! $task = $model->find($id)) {
75 | return alert('danger', 'Incorrect task ID.');
76 | }
77 |
78 | $task->type = $task->type === 'active' ? 'completed' : 'active';
79 |
80 | $model->update($task->id, [
81 | 'type' => $task->type,
82 | ]);
83 |
84 | $this->response->triggerClientEvent('taskToggled');
85 | $this->response->triggerClientEvent('checkIfThereAreTasks', '', 'swap');
86 |
87 | if ($this->request->getRawInputVar('type')) {
88 | return alert('success', 'Task updated.');
89 | }
90 |
91 | return view('Michalsn\CodeIgniterHtmxDemo\Views\tasks\task', [
92 | 'task' => $task,
93 | ]).alert('success', 'Task updated.');
94 | }
95 |
96 | /**
97 | * Toggle all tasks.
98 | */
99 | public function toggleAll()
100 | {
101 | helper('alert');
102 |
103 | $model = model(TaskModel::class);
104 |
105 | if (! $tasks = $model->findAll()) {
106 | return alert('danger', 'There are no tasks yet.');
107 | }
108 |
109 | $toggle = $this->request->getRawInputVar('toggle_all') === 'on' ? 'completed' : 'active';
110 |
111 | foreach ($tasks as $task) {
112 | $task->type = $toggle;
113 | }
114 |
115 | $model->updateBatch($tasks, 'id');
116 |
117 | $type = $this->request->getRawInputVar('type');
118 |
119 | return $this->index($type ?: null).alert('success', 'Tasks updated.');
120 | }
121 |
122 | /**
123 | * Delete task.
124 | */
125 | public function delete(int $id): string
126 | {
127 | helper('alert');
128 |
129 | if (model(TaskModel::class)->delete($id)) {
130 |
131 | $this->response->triggerClientEvent('taskDeleted');
132 | $this->response->triggerClientEvent('checkIfThereAreTasks', '', 'swap');
133 |
134 | return alert('success', 'The task has been deleted.');
135 | }
136 |
137 | return alert('danger', 'Deleting the task failed, or the task does not exist.');
138 | }
139 |
140 | /**
141 | * Delete completed tasks.
142 | */
143 | public function clearCompleted(): string
144 | {
145 | helper('alert');
146 |
147 | $this->response->triggerClientEvent('tasksCleared');
148 |
149 | if (model(TaskModel::class)->deleteCompleted()) {
150 | return alert('success', 'Completed tasks have been cleared.');
151 | }
152 |
153 | return alert('danger', 'Clearing completed tasks failed, or there are no tasks to clear.');
154 | }
155 |
156 | /**
157 | * Get tasks info.
158 | */
159 | public function summary(): string
160 | {
161 | $model = model(TaskModel::class);
162 |
163 | $data = [
164 | 'countActive' => $model->countByType('active'),
165 | 'countCompleted' => $model->countByType('completed'),
166 | ];
167 |
168 | return view('Michalsn\CodeIgniterHtmxDemo\Views\tasks\tasks_summary', $data);
169 | }
170 | }
--------------------------------------------------------------------------------
/src/Database/Migrations/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalsn/codeigniter-htmx-demo/99507f0de424a2148575caeb89e59099c85b9e02/src/Database/Migrations/.gitkeep
--------------------------------------------------------------------------------
/src/Database/Migrations/2022-10-01-080730_AddBooksTable.php:
--------------------------------------------------------------------------------
1 | forge->addField([
13 | 'id' => [
14 | 'type' => 'INT',
15 | 'constraint' => 11,
16 | 'unsigned' => true,
17 | 'auto_increment' => true,
18 | ],
19 | 'title' => [
20 | 'type' => 'VARCHAR',
21 | 'constraint' => '64',
22 | 'null' => false,
23 | ],
24 | 'author' => [
25 | 'type' => 'VARCHAR',
26 | 'constraint' => '64',
27 | 'null' => false,
28 | ],
29 | 'created_at' => [
30 | 'type' => 'DATETIME',
31 | 'default' => new RawSql('CURRENT_TIMESTAMP'),
32 | ],
33 | 'updated_at' => [
34 | 'type' => 'DATETIME',
35 | 'default' => new RawSql('CURRENT_TIMESTAMP'),
36 | ],
37 | ]);
38 |
39 | $this->forge->addKey('id', true, true);
40 | $this->forge->createTable('books');
41 | }
42 |
43 | public function down()
44 | {
45 | $this->forge->dropTable('books');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Database/Migrations/2022-12-04-110940_AddTasksTable.php:
--------------------------------------------------------------------------------
1 | forge->addField([
12 | 'id' => [
13 | 'type' => 'INT',
14 | 'constraint' => 11,
15 | 'unsigned' => true,
16 | 'auto_increment' => true,
17 | ],
18 | 'name' => [
19 | 'type' => 'VARCHAR',
20 | 'constraint' => '64',
21 | 'null' => false,
22 | ],
23 | 'type' => [
24 | 'type' => 'ENUM',
25 | 'constraint' => ['active', 'completed'],
26 | 'null' => false,
27 | 'default' => 'active'
28 | ],
29 | ]);
30 |
31 | $this->forge->addKey('id', true, true);
32 | $this->forge->createTable('tasks');
33 | }
34 |
35 | public function down()
36 | {
37 | $this->forge->dropTable('tasks');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Database/Migrations/2022-12-12-101738_AddParagraphsTable.php:
--------------------------------------------------------------------------------
1 | forge->addField([
12 | 'id' => [
13 | 'type' => 'INT',
14 | 'constraint' => 11,
15 | 'unsigned' => true,
16 | 'auto_increment' => true,
17 | ],
18 | 'title' => [
19 | 'type' => 'VARCHAR',
20 | 'constraint' => '64',
21 | 'null' => false,
22 | ],
23 | 'body' => [
24 | 'type' => 'VARCHAR',
25 | 'constraint' => '255',
26 | 'null' => false,
27 | ],
28 | 'sort' => [
29 | 'type' => 'TINYINT',
30 | 'constraint' => 1,
31 | 'null' => false,
32 | 'default' => '0'
33 | ],
34 | ]);
35 |
36 | $this->forge->addKey('id', true, true);
37 | $this->forge->createTable('paragraphs');
38 | }
39 |
40 | public function down()
41 | {
42 | $this->forge->dropTable('paragraphs');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Database/Seeds/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalsn/codeigniter-htmx-demo/99507f0de424a2148575caeb89e59099c85b9e02/src/Database/Seeds/.gitkeep
--------------------------------------------------------------------------------
/src/Database/Seeds/SeedBooksTable.php:
--------------------------------------------------------------------------------
1 | 'In Search of Lost Time', 'author' => 'Marcel Proust'],
13 | ['title' => 'Ulysses', 'author' => 'James Joyce'],
14 | ['title' => 'Don Quixote', 'author' => 'Miguel de Cervantes'],
15 | ['title' => 'One Hundred Years of Solitude', 'author' => 'Gabriel Garcia Marquez'],
16 | ['title' => 'The Great Gatsby', 'author' => 'F. Scott Fitzgerald'],
17 | ['title' => 'Moby Dick', 'author' => 'Herman Melville'],
18 | ['title' => 'War and Peace', 'author' => 'Leo Tolstoy'],
19 | ['title' => 'Hamlet', 'author' => 'William Shakespeare'],
20 | ['title' => 'The Odyssey ', 'author' => 'Homer'],
21 | ['title' => 'Madame Bovary', 'author' => 'Gustave Flaubert'],
22 | ['title' => 'Winnie-the-Pooh', 'author' => 'A.A. Milne'],
23 | ];
24 |
25 | $this->db->table('books')->insertBatch($data);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Database/Seeds/SeedDemo.php:
--------------------------------------------------------------------------------
1 | call(SeedBooksTable::class);
12 | $this->call(SeedParagraphsTable::class);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Database/Seeds/SeedParagraphsTable.php:
--------------------------------------------------------------------------------
1 | 'sentence',
17 | 'body' => 'paragraph',
18 | ];
19 |
20 | $fabricator = new Fabricator(ParagraphModel::class, $formatters);
21 |
22 | for ($i = 0; $i < 5; $i++) {
23 | $data[] = $fabricator->make();
24 | $data[$i]->sort = $i + 1;
25 | }
26 |
27 | $this->db->table('paragraphs')->insertBatch($data);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Helpers/alert_helper.php:
--------------------------------------------------------------------------------
1 |
5 |
13 | ', $type, $message);
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalsn/codeigniter-htmx-demo/99507f0de424a2148575caeb89e59099c85b9e02/src/Models/.gitkeep
--------------------------------------------------------------------------------
/src/Models/BookModel.php:
--------------------------------------------------------------------------------
1 | where('type', $type);
49 | }
50 |
51 | return $this->findAll();
52 | }
53 |
54 | /**
55 | * Count number of tasks
56 | */
57 | public function countByType(string $type): int
58 | {
59 | return $this->where('type', $type)->countAllResults();
60 | }
61 |
62 | /**
63 | * Delete completed tasks
64 | */
65 | public function deleteCompleted(): int
66 | {
67 | return $this->where('type', 'completed')->delete();
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/src/TableHelper.php:
--------------------------------------------------------------------------------
1 | baseURL = $baseURL;
20 | $this->sortColumn = $sortColumn;
21 | $this->sortDirection = $sortDirection;
22 | }
23 |
24 | public function setValidSortColumns(array $data): TableHelper
25 | {
26 | $this->validSortColumns = $data;
27 |
28 | return $this;
29 | }
30 |
31 | public function baseURL(): string
32 | {
33 | $queryString = [
34 | 'sortColumn' => $this->sortColumn,
35 | 'sortDirection' => $this->sortDirection,
36 | ];
37 |
38 | return $this->baseURL . '?' . http_build_query($queryString);
39 | }
40 |
41 | public function sortByURL(string $column): string
42 | {
43 | if ($this->validSortColumns !== [] && ! in_array($column, $this->validSortColumns)) {
44 | throw new InvalidArgumentException('Sort column is out of the range.');
45 | }
46 |
47 | $queryString = [
48 | 'sortColumn' => $column,
49 | 'sortDirection' => $this->sortColumn === $column && $this->sortDirection === 'asc' ? 'desc' : 'asc',
50 | ];
51 |
52 | return $this->baseURL . '?' . http_build_query($queryString);
53 | }
54 |
55 | public function getSortIndicator(string $column): string
56 | {
57 | if ($this->validSortColumns !== [] && ! in_array($column, $this->validSortColumns)) {
58 | throw new InvalidArgumentException('Sort column is out of the range.');
59 | }
60 |
61 | if ($column === $this->sortColumn) {
62 | return $this->sortDirection === 'asc' ? '↑' : '↓';
63 | }
64 |
65 | return '';
66 | }
67 | }
--------------------------------------------------------------------------------
/src/Views/Pager/default_htmx_full.php:
--------------------------------------------------------------------------------
1 | setSurroundCount(2);
9 | ?>
10 |
11 |
--------------------------------------------------------------------------------
/src/Views/books/index.php:
--------------------------------------------------------------------------------
1 | extend('Michalsn\CodeIgniterHtmxDemo\Views\layout') ?>
2 |
3 | section('title') ?>
4 | CodeIgniter HTMX Demo - Books
5 | endSection() ?>
6 |
7 | section('content') ?>
8 |
9 |
10 |
11 |
12 |
13 |
14 | = $this->include('Michalsn\CodeIgniterHtmxDemo\Views\books\table'); ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 | endSection('content') ?>
--------------------------------------------------------------------------------
/src/Views/books/table.php:
--------------------------------------------------------------------------------
1 |
2 |
37 |
38 |
39 |
40 |
No books
41 |
42 | No books found :(
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | = anchor($table->sortByURL('id'), 'ID', ['hx-get' => site_url($table->sortByURL('id'))]); ?> = $table->getSortIndicator('id'); ?>
51 | = anchor($table->sortByURL('title'), 'Title', ['hx-get' => site_url($table->sortByURL('title'))]); ?> = $table->getSortIndicator('title'); ?>
52 | = anchor($table->sortByURL('author'), 'Author', ['hx-get' => site_url($table->sortByURL('author'))]); ?> = $table->getSortIndicator('author'); ?>
53 |
54 |
55 |
56 |
57 |
58 | = $this->setVar('book', $book)->include('Michalsn\CodeIgniterHtmxDemo\Views\books\table_row'); ?>
59 |
60 |
61 |
62 |
63 |
64 | = $pager->links('default', 'default_htmx_full'); ?>
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/Views/books/table_row.php:
--------------------------------------------------------------------------------
1 |
2 | = $book->id; ?>
3 | = esc($book->title); ?>
4 | = esc($book->author); ?>
5 |
6 |
7 |
8 | Edit
9 |
10 |
11 |
12 | Delete
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Views/books/table_row_add.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | New
4 |
5 |
6 | = form_input('title', set_value('title'), ['class' => $validation->hasError('title') ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm']); ?>
7 |
8 | = $validation->getError('title'); ?>
9 |
10 |
11 |
12 | = form_input('author', set_value('author'), ['class' => $validation->hasError('author') ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm']); ?>
13 |
14 | = $validation->getError('author'); ?>
15 |
16 |
17 |
18 |
19 |
20 | Add
21 |
22 |
23 |
24 | Cancel
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Views/books/table_row_edit.php:
--------------------------------------------------------------------------------
1 |
2 | = $book->id; ?>
3 |
4 | = form_input('title', set_value('title', $book->title), ['class' => $validation->hasError('title') ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm']); ?>
5 |
6 | = $validation->getError('title'); ?>
7 |
8 |
9 |
10 | = form_input('author', set_value('author', $book->author), ['class' => $validation->hasError('author') ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm']); ?>
11 |
12 | = $validation->getError('author'); ?>
13 |
14 |
15 |
16 |
17 |
18 | Save
19 |
20 |
21 |
22 | Cancel
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Views/cells/index.php:
--------------------------------------------------------------------------------
1 | extend('Michalsn\CodeIgniterHtmxDemo\Views\layout') ?>
2 |
3 | section('title') ?>
4 | CodeIgniter HTMX Demo - Controlled Cells
5 | endSection() ?>
6 |
7 | section('content') ?>
8 |
9 |
10 |
11 |
12 |
13 |
14 | = view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\Counter\CounterCell'); ?>
15 |
16 |
17 |
18 | = view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\Counter\CounterCell', ['count' => 5]); ?>
19 |
20 |
21 |
22 |
23 |
24 | = view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\TableSimple\TableSimpleCell'); ?>
25 |
26 |
27 |
28 |
29 |
30 | = view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\TableAdvanced\TableAdvancedCell', ['page' => 2]); ?>
31 |
32 |
33 |
34 |
35 |
36 |
37 | = view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\CounterSigned\CounterSignedCell'); ?>
38 |
39 |
40 |
41 | = view_cell('Michalsn\CodeIgniterHtmxDemo\Cells\CounterSigned\CounterSignedCell', ['count' => 5, 'step' => 5]); ?>
42 |
43 |
44 |
45 |
46 |
47 |
Did you know?
48 |
A
Signed URL library will prevent manual URL manipulation. It can come in handy on many occasions. You can learn more
here .
49 |
50 |
51 |
52 |
53 |
54 |
55 |
Did you know?
56 |
If you install the
codeigniter-signed-url library, you will get access to the new example for the
Counter Cell , which will prevent manual URL manipulation.
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | endSection('content') ?>
--------------------------------------------------------------------------------
/src/Views/home.php:
--------------------------------------------------------------------------------
1 | extend('Michalsn\CodeIgniterHtmxDemo\Views\layout') ?>
2 |
3 | section('title') ?>
4 | CodeIgniter HTMX Demo - Dashboard
5 | endSection() ?>
6 |
7 | section('content') ?>
8 |
9 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
1. Migrate the database
37 |
38 |
php spark migrate --all
39 |
40 |
2. Load sample data
41 |
42 | For Unix
43 |
44 |
45 |
php spark db:seed Michalsn\\CodeIgniterHtmxDemo\\Database\\Seeds\\SeedDemo
46 |
47 |
48 | For Windows
49 |
50 |
51 |
php spark db:seed Michalsn\CodeIgniterHtmxDemo\Database\Seeds\SeedDemo
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
64 |
65 |
66 |
67 | Books (searching, pagination, inline edit)
68 |
69 |
70 | Tasks (events)
71 |
72 |
73 | Paragraphs (sorting, modal edit)
74 |
75 |
76 | Controlled Cells (widget like components, signed-urls)
77 |
78 |
79 |
80 |
81 | Note that the demos were prepared to show as many HTMX features as possible. And they do not always take the most optimal path to achieve the goal.
82 |
83 |
84 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | endSection() ?>
--------------------------------------------------------------------------------
/src/Views/layout.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | = csrf_meta(); ?>
8 |
9 |
= $this->renderSection('title') ?>
10 |
11 |
12 |
13 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
92 |
93 |
94 |
95 |
96 |
172 |
173 |
174 | = $this->renderSection('content') ?>
175 |
176 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
--------------------------------------------------------------------------------
/src/Views/paragraphs/edit.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | = form_open('paragraphs/edit/' . $paragraph->id, [
6 | 'hx-post' => site_url('paragraphs/edit/' . $paragraph->id),
7 | 'hx-include' => 'closest .modal-content', 'hx-target' => '#paragraphs', 'hx-swap' => 'outerHTML'
8 | ]); ?>
9 |
12 |
13 | fragment('fields'); ?>
14 |
15 |
16 |
Title
17 |
18 |
19 | = $validation->getError('title'); ?>
20 |
21 |
22 |
23 |
24 |
25 |
Body
26 |
27 |
28 | = $validation->getError('body'); ?>
29 |
30 |
31 |
32 | endFragment(); ?>
33 |
34 |
40 | = form_close(); ?>
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/Views/paragraphs/index.php:
--------------------------------------------------------------------------------
1 | extend('Michalsn\CodeIgniterHtmxDemo\Views\layout') ?>
2 |
3 | section('title') ?>
4 | CodeIgniter HTMX Demo - Paragraphs
5 | endSection() ?>
6 |
7 | section('content') ?>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
25 | fragment('paragraphs'); ?>
26 |
27 | = form_open('', [
28 | 'class' => 'sortable', 'hx-post' => site_url('paragraphs/reorder'),
29 | 'hx-trigger' => 'end', 'hx-swap' => 'outerHTML', 'hx-target' => '#paragraphs',
30 | 'hx-indicator' => '#indicator'
31 | ]); ?>
32 |
33 |
34 |
35 |
36 |
42 |
43 |
= esc($p->title); ?>
44 |
= esc($p->body); ?>
45 |
46 |
56 |
57 |
58 |
59 |
60 | = form_close(); ?>
61 |
62 | endFragment(); ?>
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | endSection('content') ?>
--------------------------------------------------------------------------------
/src/Views/tasks/index.php:
--------------------------------------------------------------------------------
1 | extend('Michalsn\CodeIgniterHtmxDemo\Views\layout') ?>
2 |
3 | section('title') ?>
4 | CodeIgniter HTMX Demo - Tasks
5 | endSection() ?>
6 |
7 | section('content') ?>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 | fragment('tasks'); ?>
42 |
hx-get="= site_url('tasks/' . $type); ?>" hx-swap="outerHTML" hx-trigger="tasksCleared from:body">
43 |
59 |
60 |
61 |
No = $type ?> Tasks
62 |
63 | There are no = $type ?> tasks yet.
64 |
65 |
66 |
67 |
68 | = $this->setVar('task', $task)->include('Michalsn\CodeIgniterHtmxDemo\Views\tasks\task'); ?>
69 |
70 |
71 |
74 |
75 | endFragment(); ?>
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | endSection('content') ?>
--------------------------------------------------------------------------------
/src/Views/tasks/task.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | type === 'completed' ? 'checked' : ''; ?>
7 | hx-put="= site_url('tasks/toggle/' . $task->id); ?>"
8 | hx-include="[name='type']"
9 | hx-target="closest .list-group-item"
10 | hx-trigger="click"
11 | hx-swap="outerHTML">
12 |
13 |
14 | = esc($task->name); ?>
15 |
16 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Views/tasks/tasks_summary.php:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 | = $countActive; ?> = $countActive === 1 ? 'task' : 'tasks'; ?> left
13 |
14 |
23 |
--------------------------------------------------------------------------------