trying to learn Vue js and I'm building a Todo list app everything is working but I have a problem with something, for example when I click on active I want to show the tasks that are not done or finished and so on, here is the code, I tried to use code snippet but it does not work because it does not support Vue js 3 yet
<template>
<section class="header">
</section>
<div class="container">
<div class="title-theme">
<h1>Todo</h1>
<input type="checkbox" id="switch-l" class="themeSwitch">
<label #click="switchTheme" for="switch-l" id="switch" class="themeSwitch-label"></label>
</div>
<div class="todosInput">
<div id="mark"></div>
<form #submit.prevent="addNewTodo" class="form" action="" data-new-todo-form>
<input v-model="newTodo" name="newTodo" id="todos" data-new-todo-input type="text" placeholder="Create a new todo..." >
</form>
</div>
<div class="myTodos">
<ul id="todo-list">
<li v-for="(todo, index) in todos" :key="todo.id" class="todo-item ">
<input #click="toggleDone(todo)" class="js-tick" id="1610198328386" type="checkbox">
<span :class="{ done: todo.done }">{{todo.task}}</span>
<img #click="deleteTodo(index)" class="delete" width="15px" height="15px" src="/images/icon-cross.svg" alt="cross">
</li>
</ul>
</div>
<div class="controls">
<p id="todosLeft">{{}}items left</p><!-- Add dynamic number -->
<div class="controls-list-div">
<ul class="controls-list" data-lists>
<li id="All">All</li>
<li id="Active">Active</li>
<li id="Completed">Completed</li>
</ul>
</div>
<p id="Clear-completed">Clear Completed</p>
</div>
</div>
<div class="instruc">
<p>Drag and drop to reorder list</p>
</div>
<div class="attribution">
Challenge by Frontend Mentor.
Coded by Your Name Here.
</div>
</template>
import {ref} from 'vue';
export default {
setup() {
const newTodo = ref('');
const todos = ref([]);
const addNewTodo = () => {
todos.value.push({
done: false,
task: newTodo.value,
id: Date.now(),
});
newTodo.value = ''
}
const toggleDone = (todo) => {
todo.done = !todo.done
}
const deleteTodo = (index) => {
todos.value.splice(index, 1)
}
const switchTheme = () => {
const body = document.body
if (body.classList.contains('light')) {
body.classList.replace('light', 'dark')
} else {
body.classList.replace('dark', 'light')
}
}
return {
addNewTodo,
newTodo,
todos,
toggleDone,
deleteTodo,
switchTheme,
}
}
}
Instead of rendering todos in v-for, introduce computed (named maybe filteredTodos) and render that. Introduce new ref named filter with possible values of all/active/completed. Implement filtering inside filteredTodos based on the value of filter...
let app = Vue.createApp({
setup() {
const newTodo = Vue.ref('');
const filter = Vue.ref('all')
const todos = Vue.ref([
{ task: "Task 1", done: false, id: 1 },
{ task: "Task 2", done: false, id: 2 }
]);
const filteredTodos = Vue.computed(() => {
const filterFn = filter.value === 'active' ? (item) => !item.done :
filter.value === 'completed' ? (item) => item.done : (item) => true;
return todos.value.filter(filterFn)
})
const addNewTodo = () => {
todos.value.push({
done: false,
task: newTodo.value,
id: Date.now(),
});
newTodo.value = ''
}
const toggleDone = (todo) => {
todo.done = !todo.done
}
const deleteTodo = (index) => {
todos.value.splice(index, 1)
}
const switchTheme = () => {
const body = document.body
if (body.classList.contains('light')) {
body.classList.replace('light', 'dark')
} else {
body.classList.replace('dark', 'light')
}
}
return {
addNewTodo,
newTodo,
todos,
toggleDone,
deleteTodo,
switchTheme,
filter,
filteredTodos
}
}
})
app.mount("#app")
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.5/vue.global.prod.js" integrity="sha512-7mjRUL9551cOFF57PSrURwSa9UsUmufUCU9icwUEoUrECcxpa20PakbPplb7b4ZGbCc0StIr9ytHoXH9+v6ygA==" crossorigin="anonymous"></script>
<div id="app">
<div class="myTodos">
{{ filter }}
<ul id="todo-list">
<li v-for="(todo, index) in filteredTodos" :key="todo.id" class="todo-item ">
<input #click="toggleDone(todo)" class="js-tick" id="1610198328386" type="checkbox" :checked="todo.done">
<span :class="{ done: todo.done }">{{todo.task}}</span>
</li>
</ul>
</div>
<div class="controls">
<ul class="controls-list" data-lists>
<li id="All"><a #click="filter = 'all'" href="#">All<a/></li>
<li id="Active"><a #click="filter = 'active'" href="#">Active<a/></li>
<li id="Completed"><a #click="filter = 'completed'" href="#">Completed<a/></li>
</ul>
</div>
</div>
Related
I trying to pass variable tab in showTab in x-for my code look like this
<div x-data ="tabs">
<ul >
<template x-for="tab in items">
<li>
<button #click="showtabs('tab')"></button>
<button x-text= "tab" #click="tab.current = 'second'"></button>
</li>
</template>
</ul>
<div x-show='current'>
fsfsdfds
</div>
<div x-show='current'>
nice
</div>
in my script below I want to pass parameters in showtab()
document.addEventListener('alpine:init', () => {
Alpine.data('tabs', () => ({
open: false,
current: 'first',
items: ['first', 'second'],
showtabs(data_id){
for(keys in items){
var obj = items[keys]
if(obj == data_id){
}
}
}
}))
})
To pass a variable from template to function you can use Template literals and send `${expression}`
for example:
<div x-data="tabs">
<ul class="nav nav-tabs">
<template x-for="tab in items">
<li class="nav-item">
<button #click="showTabs(`${tab}`)" x-text="tab"></button>
</li>
</template>
</ul>
<div x-show="current === 'first'">
fsfsdfds
</div>
<div x-show="current === 'second'">
nice
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('tabs', () => ({
open: false,
current: 'first',
items: ['first', 'second'],
showTabs(data_id) {
this.current = data_id;
}
}))
})
</script>
I am looping through an array on v-for loop in vue 3. Is there a way to toggle the v-show of a paragraph by clicking on the Heading element. Below is my code :
<div class="defs">
<ul v-for="(d, index) in definitions"
:key="'d' + index">
<li>
<div class="project" >
<div #click="showDetails" class="actions">
<h3>{{ d.title }}</h3>
<div class="icons">
<span class="material-icons" ><i class="fas fa-trash-alt"></i></span>
<span class="material-icons" ><i class="fas fa-pencil-alt"></i></span>
</div>
</div>
<div v-if="show" class="details">
<p>{{d.explanation}}</p>
</div>
</div>
</li>
</ul>
</div>
<script>
import { ref } from "vue";
import { projectDatabase} from '../../firebase/config'
export default {
props: ['id'],
setup(props){
const show = ref(false);
const showDetails = () =>{
show.value = !show.value
}
return {
definitions, props, show, showDetails,
}
}
}
</script>
I know we cant use this in composition API. so how can we solve the toggle issue ?
Try like following snippet, here is the codesandbox with composition API
const demo = {
data() {
return {
definitions: [
{ title: "aaa", explanation: "aaa" },
{ title: "bbb", explanation: "bbb" },
{ title: "ccc", explanation: "ccc" },
],
show: null
}
},
methods: {
showDetails(idx) {
this.show === idx ? (this.show = null) : (this.show = idx);
}
},
// same code with coposition API
/*import { ref } from "vue";
import { projectDatabase} from '../../firebase/config'
setup() {
const show = ref(null);
const definitions = ref([
{ title: "aaa", explanation: "aaa" },
{ title: "bbb", explanation: "bbb" },
{ title: "ccc", explanation: "ccc" },
]);
const showDetails = (idx) => {
show.value === idx ? (show.value = null) : (show.value = idx);
};
return { definitions, show, showDetails }
},*/
};
Vue.createApp(demo).mount("#demo");
<script src="https://unpkg.com/vue#next"></script>
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous" />
<div id="demo">
<div class="defs">
<ul>
<li v-for="(d, index) in definitions" :key="index">
<div class="project">
<div #click="showDetails(index)" class="actions">
<h3>{{ d.title }}</h3>
<div class="icons">
<span class="material-icons"
><i class="fas fa-trash-alt"></i
></span>
<span class="material-icons"
><i class="fas fa-pencil-alt"></i
></span>
</div>
</div>
<div v-if="show === index" class="details">
<p>{{ d.explanation }}</p>
</div>
</div>
</li>
</ul>
</div>
</div>
I am trying to learn react js , but I don't know how to delete an item from my list, could you help me ? Actually, I am not professional , but I am really interested in learning, there is my codes which add some new item to my list properly, but I don't know how to delete them when I click on their checkbox/delete button.
import React, { useState } from "react";
const App = () => {
const [NewTaskText, setNewTaskText] = useState("");
const [Tasks, setTasks] = useState(["do a task", "samira"]);
const addTask = (e) => {
e.preventDefault();
if (!NewTaskText) return;
setTasks((currentTasks) => {
return [...currentTasks, NewTaskText];
});
setNewTaskText("");
};
const onchangeInput = (e) => {
const value = e.target.value;
setNewTaskText(value);
};
return (
<div className="row">
<form onSubmit={addTask}>
<div className="row col s6">
<div className="input-field col s10">
<textarea
id="textarea1"
className="materialize-textarea"
value={NewTaskText}
onChange={onchangeInput}
></textarea>
<label htmlFor="textarea1">What needs to be done ?</label>
</div>
</div>
<div className="row col s6">
<br></br>
<a className='waves-effect waves-light btn' href="/#" onClick={addTask}>
<i className='material-icons left' >
add_circle
</i>
Add
</a>
</div>
</form>
<div className="row">
<div className="row col s9">
<ul className="collection with-header">
<li className="collection-header">
<h4>Todo List</h4>
<form>
<div className="input-field">
<input id="search" type="search" required />
<label className="label-icon" htmlFor="search">
<i className="material-icons">search</i>Search
</label>
<i className="material-icons">close</i>
</div>
</form>
</li>
<label>
{Tasks.map((item, i) => {
return (
<li className="collection-item" key={i}>
<input type="checkbox" />
<span>
{item}
</span>
<span>
<a href="#!" className="secondary-content">
<i className="material-icons" >delete</i>
<i className="material-icons">check_circle</i>
</a>
</span>
</li>
);
})}
</label>
</ul>
</div>
</div>
</div>
);
};
export default App;
You can delete the item from the array by using index:
<i className="material-icons" onClick={() => handleDelete(i)}>delete</i>
and define your function above the return statement:
const handleDelete = i => {
const taskList = [...Tasks]
taskList.splice(i, 1)
setTasks(taskList)
}
Here it is important to know that you have to make a copy of the state if it is an object or an array. Because if you modify the original array or object, react won't register that as a change, and won't re-render. That is why I did const taskList = [...Tasks]. You can also use a library like lodash which provides some neat ways of handling objects and arrays
Just add a onClick handler to button which should delete item
<i className="material-icons" onClick={ () => {
setTasks((prevTasks) => {
const updatedPrevTasks = [...prevTasks];
updatedPrevTasks.splice(i, 1);
return updatedPrevTasks;
});
}}>delete</i>
I am new to Vue. I am building a simple app that will list all countries and when you click on a particular country it shows you more details about the country. Idea is to open country details in a modal.
I'm stuck with displaying that modal. The modal opens, but in the background. It also opens a detail page.
CountryDetail.vue:
<script>
import axios from 'axios';
export default {
name: 'country-detail',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
alpha3Code: [],
alpha3CodetoString: [],
}
},
mounted () {
this.pending = true;
axios
.get(`https://restcountries.eu/rest/v2/name/${this.$route.params.country}?fullText=true`)
.then((response) => {
(this.countryInfo = response.data)
this.alpha3CodetoString = this.alpha3Code.join(';');
})
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
}
}
</script>
<template>
<modal v-model="show">
<div class="modal-mask" :class="{ darkTheme : isDarkTheme }" name="modal">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
</slot>
</div>
<div v-for="country in countryInfo" class="countryTile modal-body" v-bind:key="country.id">
<slot name="body">
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="country-details">
<h1>{{country.name}}</h1>
<div class="listDiv">
<ul>
<li><span>Population:</span> {{country.population | formatNumbers }}</li>
<li><span>Capital:</span> {{country.capital}}</li>
<li><span>Iso:</span> {{country.alpha3Code}}</li>
</ul>
<ul>
<li><span>Currencies:</span> {{country.currencies['0'].name}}</li>
<li><span>Languages:</span>
<span
v-for="(language, index) in country.languages"
v-bind:key="index"
class="languages">
{{language.name}}<span v-if="index + 1 < country.languages.length">, </span>
</span>
</li>
</ul>
</div>
</div>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<a #click="$router.go(-1)" class="backBtn"><i class="fas fa-arrow-left" />Go Back</a>
</slot>
</div>
</div>
</div>
</div>
</modal>
</template>
Home.vue:
<script>
import axios from 'axios';
export default {
name: 'home',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
search: '',
darkMode: false,
}
},
mounted () {
this.pending = true;
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => (this.countryInfo = response.data))
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
},
computed: {
filteredCountries: function () {
return this.countryInfo.filter((country) => {
if (this.region === '' ) {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else if (this.search !== '') {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else {
return ('blbla');
}
})
}
},
}
</script>
<template>
<div class="home" :class="{ darkTheme : isDarkTheme }">
<div class="searchBar">
<div class="searchContainer">
<i class="fas fa-search searchIcon"></i>
<input
class="searchInput"
type="text"
v-model="search"
aria-label="Search for a country..."
placeholder="Search for a country..."
/>
<ul class="searchResults"></ul>
</div>
</div>
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
<div v-if="countryInfo" class="tileGrid" #click="showModal = true">
<div v-for="country in filteredCountries" class="countryTile" v-bind:key="country.id">
<router-link
:to="{ name: 'country-detail', params: {country: country.name }}"
class="linkTile"
>
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="text">
<h1>{{ country.name }}</h1>
</div>
</router-link>
</div>
</div>
</div>
</template>
The router-link will always redirect you to another page, because its basically <a href="..."> see here. You don't need router if you just want to show the detail on a modal, you could just add the modal component inside the Home.vue component, then bind the modal and the countryName with props, then pass them in when clicking a button.
Home.vue:
<template>
<div>
<button #click="showDetail">
Show Detail
</button>
<CountryDetail :countryName="countryName" :showModal="showModal"/>
<div>
</template>
<script>
import CountryDetail from './CountryDetail.vue'
export default {
name: 'Home',
components: { CountryDetail },
data: () => ({
countryName: '',
showModal: false,
}),
methods: {
showDetail() {
this.showModal = true;
},
},
}
</script>
And instead of making request on mounted, you could use watch to do something like watching for the showModal prop, and make request everytime it has a truthy value. Like this:
CountryDetail.vue:
<template>
<modal v-model="showModal">
<!-- modal content -->
</modal>
</template>
<script>
export default {
name: 'CountryDetail',
props: ['countryName', 'showModal'],
watch: {
'showModal': {
deep: true,
handler(val) {
if (val && this.countryName !== '') {
// Make request
}
}
}
}
}
</script>
Here i tried to change state using edit button. I'm not sure how to edit it with double click an then save it on clicking save button.I made save function but it is not working as directed. I need the new value to be updated in the list after clicking save
class App extends React.Component {
constructor(){
super();
this.state={
todo:[],
editing:false,
value: ''
};
};
entertodo(keypress){
var Todo=this.refs.inputodo.value;
if( keypress.charCode == 13 )
{
this.setState({
todo: this.state.todo.concat({Value:Todo, checked:false})
});
this.refs.inputodo.value=null;
};
};
todo(todo,i){
return (
<li className={todo.checked===true? 'line':'newtodo'}>
<div onClick={this.todoCompleted.bind(this, i)}>
<input type="checkbox" className="option-input checkbox" checked={todo.checked} />
<div key={todo.id} className="item">
{todo.Value}
<button onClick={this.edit.bind(this,i)} className="editmode">edit</button>
<span className="destroy" onClick={this.remove.bind(this, i)}>X</span>
</div>
</div>
</li>
);
};
remove(i){
this.state.todo.splice(i,1)
this.setState({todo:this.state.todo})
};
todoCompleted(i){
var todo=this.state.todo;
todo[i].checked =todo[i].checked? false:true;
this.setState({
todo:this.state.todo
});
};
allCompleted=()=>{
var todo = this.state.todo;
var _this = this
todo.forEach(function(item) {
item.className = _this.state.finished ? "newtodo" : "line"
item.checked = !_this.state.finished
})
this.setState({todo: todo, finished: !this.state.finished})
};
changeValue(e) {
this.setState({
value: this.state.value = e.target.value
});
}
edit(i){
var todo= this.state.todo
this.setState({
editing:true,
value: this.state.todo[i].Value
});
};
save(i){
var Todo=this.ref.edittodo.value
var todo=this.state.todo[i]
this.setState({
editing:false,
todo:this.state.todo.concat({Value:todo})
});
};
rendernormal() {
return (
<div>
<h1 id='heading'>todos</h1>
<div className="lines"></div>
<div>
<input type="text" ref= "inputodo" onKeyPress={this.entertodo.bind(this)}className="inputodo"placeholder='todos'/>
<span onClick={this.allCompleted}id="all">^</span>
</div>
<div className="mainapp">
<ul className="decor">
{this.state.todo.map(this.todo.bind(this))}
</ul>
</div>
</div>
);
};
renderform(todo,i) {
return (
<div>
<h1 id='heading'>todos</h1>
<div className="lines"></div>
<div>
<input type="text" ref= "edittodo" value={this.state.value} onChange={this.changeValue.bind(this)} className="editodo"placeholder='EDIT TODO'/>
<span onClick={this.allCompleted}id="all">^</span>
<button onClick={this.save.bind(this,i)}>save</button>
</div>
<div className="mainapp">
<ul className="decor">
{this.state.todo.map(this.todo.bind(this))}
</ul>
</div>
</div>
);
};
render(){
if (this.state.editing) {
return this.renderform()
}
else {
return this.rendernormal()
}
};
}
ReactDOM.render(<App/>,document.getElementById('app'));
.line {
text-decoration: line-through;
color: red;
}
.newtodo{
text-decoration: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="app"></div>
I tried some ways but the error i got is cannot read property 'edittodo' of undefined
The problem is that this.ref is undefined in this line:
this.ref.edittodo.value
What you're looking for is this.refs (source):
this.refs.edittodo.value
What's better is to stop using the string as a ref, and use a callback, as explained in the link above:
<input type="text"
ref={(input) => this.edittodo = input}
value={this.state.value}
onChange={this.changeValue.bind(this)}
className="editodo"placeholder='EDIT TODO'/>
And then you can access the input like this:
this.edittodo