49
loading...
This website collects cookies to deliver better user experience
npm i yarn -g
npx create-strapi-app strapi-todo-api --quickstart
#or
yarn create strapi-app strapi-todo-api --quickstart
cd
into the project directory and run one of the following commands:npm run develop
#or
yarn develop
Todo
collection so let's go ahead and add documents to our newly created collections.Todo
collection, one for our todo items and the other a boolean to check the state of our todo; whether it is marked as done. isCompleted
. We will be using it to toggle our todo state when a user checks on the completed checkbox in our App.Setting
→ Users & Permissions Plugin
→ and then tick on the select all checkbox under the Permissions
section and click on the Save
button:[http://localhost:1337/todos](http://localhost:1337/todos)
in your browser or using an API client like Postman to test our created data.npx degit sveltejs/template SvelteTodoApp
cd SvelteTodoApp
npm install
npm run dev
[http://localhost:54890](http://localhost:54890)
from your browser, and you should see this:src/Todo.svelte
and replace everything in the main
tag in App.svelte
with the following:<Todo />
npm install tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
# or
yarn add tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
# and
npx tailwindcss init tailwind.config.js
tailwind.config.js
and rollup.config.js
file in the root of our app. Replace the code in your tailwind.config.js
file with the following:const production = !process.env.ROLLUP_WATCH;
module.exports = {
purge: {
content: ['./src/**/*.svelte'],
enabled: production,
},
darkMode: false,
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
future: {
purgeLayersByDefault: true,
removeDeprecatedGapUtilities: true,
},
};
rollup.config.js
and replace what you have with the following:import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';
import sveltePreprocess from 'svelte-preprocess';
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn(
'npm',
['run', 'start', '--', '--dev'],
{
stdio: ['ignore', 'inherit', 'inherit'],
shell: true,
}
);
process.on('SIGTERM', toExit);
process.on('exit', toExit);
},
};
}
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js',
},
plugins: [
svelte({
preprocess: sveltePreprocess({
sourceMap: !production,
postcss: {
plugins: [require('tailwindcss'), require('autoprefixer')],
},
}),
compilerOptions: {
// enable run-time checks when not in production
dev: !production,
},
}),
// we'll extract any component CSS out into
// a separate file - better for performance
css({ output: 'bundle.css' }),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte'],
}),
commonjs(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser(),
],
watch: {
clearScreen: false,
},
};
App.svelte
:<style global lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
npm install axios
#or
yarn add axios
import { onMount } from 'svelte';
import axios from 'axios';
let isError = null;
let todos = [];
[onMount](https://svelte.dev/docs#onMount)
is a lifecycle function in Svelte that schedules a callback to run immediately after the component is mounted to the DOM. The todos
array is where all the data we get from our API will be.getTodo
and call it on the onMount
lifecycle function:const getTodos = async () => {
try {
const res = await axios.get('http://localhost:1337/todos');
todos = res.data;
} catch (e) {
isError = e;
}
};
onMount(() => {
getTodos();
});
getTodos
function will make an asynchronous call to the endpoint we created earlier and set the result of our todos array to be the data from our API.// Todo.svelte
{#if todos.length > 0}
<p class="text-2xl mb-4">Today's Goal</p>
{/if}
{#if isError}
<p class="text-xl mb-2 text-red-600">{isError}</p>
{/if}
<ul>
{#each todos as todo}
<li
class="rounded-xl bg-black bg-opacity-10 p-5 mb-4 flex items-center justify-between cursor-pointer hover:shadow-lg transition transform hover:scale-110"
>
<div class="flex items-center w-full">
<input
type="checkbox"
class="mr-3"
bind:checked={todo.isCompleted}
on:click={toggleComplete(todo)}
/>
<input
class:completed={todo.isCompleted}
class="border-0 bg-transparent w-full"
bind:value={todo.todoItem}
on:change={updateTodo(todo)}
on:input={updateTodo(todo)}
/>
</div>
<button on:click={deleteTodo(todo)} class="border-0"
><img src={deletIcon} alt="delete todo" class="w-6 " /></button
>
</li>
{:else}
<p>No goals for today!</p>
{/each}
</ul>
todos
array and then conditionally a text, We are also doing the same thing with the isError
checking if there’s an error from our API.todoItem
and then we use the {:
[else](https://svelte.dev/docs#each)
}...{/each}
to conditionally render a text when there’s no result. Notice we haven’t created the updateTodo
and deleteTodo
function. We will do that later
let todoItem = '';
const addTodo = async () => {
try {
if (!todoItem) return alert('please add a goal for today!');
const res = await axios.post('http://localhost:1337/todos', {
todoItem,
isCompleted: false,
});
// Using a more idiomatic solution
todos = [...todos, res?.data];
todoItem = '';
} catch (e) {
isError = e;
}
todoItem
variable is what we will use to get the user's input, and then in our addTodo
function, we are making sure it's not empty before making a POST request to our todos endpoint.todoItem
and setting isCompleted: false
because we want the todo to be undone when created. Finally, we are updating our todos array with the data coming in from the API call.<input
type="text"
bind:value={todoItem}
class="w-full rounded-xl bg-white border-0 outline-none bg-opacity-10 p-4 shadow-lg mt-4"
placeholder="Add new goals"
/>
<button
on:click={addTodo}
class="my-5 p-5 bg-black text-white rounded-xl w-full hover:bg-opacity-60 transition border-0 capitalize flex items-center justify-center"
><span><img src={addIcon} alt="add todo" class="w-6 mr-4" /></span>Add new
todo</button>
bind:value={todoItem}
in our input field above. This binding is used to achieve two-way data binding in Svelte.const toggleComplete = async (todo) => {
const todoIndex = todos.indexOf(todo);
try {
const { data } = await axios.put(
`http://localhost:1337/todos/${todo.id}`,
{
isCompleted: !todo.isCompleted,
}
);
todos[todoIndex].isCompleted = data.isCompleted;
} catch (e) {
isError = e;
}
};
indexOf
method and then making a PUT request to the server to update the particular todo item. isCompleted
field in our API by sending isCompleted: !todo.isCompleted
in our request. When our API is resolved, we update our todos array in our state with the payload from our API by setting todos[todoIndex].isCompleted = data.isCompleted
;const updateTodo = async (todo) => {
const todoIndex = todos.indexOf(todo);
try {
const { data } = await axios.put(
`http://localhost:1337/todos/${todo.id}`,
{
todoItem: todo.todoItem,
}
);
todos[todoIndex].todoItem = data.todoItem;
} catch (e) {
isError = e;
}
};
updateTodo
function does almost the same thing as the toggleComplete
except that it updates the todo text. <div class="flex items-center w-full">
<input
type="checkbox"
class="mr-3"
bind:checked={todo.isCompleted}
on:click={toggleComplete(todo)}
/>
<input
class:completed={todo.isCompleted}
class="border-0 bg-transparent w-full"
bind:value={todo.todoItem}
on:change={updateTodo(todo)}
on:input={updateTodo(todo)}
/>
</div>
[bind:value={}](https://svelte.dev/docs#bind_element_property)
syntax provided to us by Svelte.class:completed={todo.isCompleted}
. We are telling Svelte that it should add the completed
class whenever todo.isCompleted
is truthy. <style>
.completed {
text-decoration: line-through;
}
</style>
todos
array:const deleteTodo = async (todo) => {
try {
await axios.delete(`http://localhost:1337/todos/${todo.id}`);
todos = todos.filter((to) => to.id !== todo.id);
} catch (e) {
isError = e;
}
delete
method on Axios
and then appending the id
value of the todo item the user clicks to our URL. This call will effectively remove the item clicked from our todos
collection in our API, and then we are filtering our todos
array and returning all the todos except for the one deleted.<button on:click={deleteTodo(todo)} class="border-0"><img src={deletIcon} alt="delete todo" class="w-6 " /></button>