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>
Related
I have a code in Vue that creates the elements of a menu using a v-for, and each element must have a different method when it's clicked.
So far I have this:
<span v-for="(menuItem, index) in menuItems" :key="index">
<li>
<a id="listItem">
<i class="bx" :class="menuItem.icon || 'bx-square-rounded'" />
<span class="links_name" v-on:click=menuItem.click>{{ menuItem.name }}</span>
</a>
<span class="tooltip">{{
menuItem.tooltip || menuItem.name
}}</span>
</li>
</span>
How can I assign different funcions on the v-on?
You can make the listener function as a node in the object list.
Sample Implementation
const app = new Vue({
el: '#app',
data() {
return {
list: [{
name: 'lg',
age: 27,
onClick: function() {
console.log("First Item Clicked");
}
}, {
name: 'camile',
age: 27,
onClick: function() {
console.log("Second Item Clicked");
}
}]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.0/vue.js"></script>
<body>
<div id="app">
<ul>
<li v-for="(item, index) in list" :key="item.name">
{{item.name}} - {{item.age}}
<button #click="item.onClick()">
Click Me
</button>
</li>
</ul>
<input type="text" v-model="list[0].name">
</div>
</body>
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>
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>
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>
This question already has answers here:
A way to render multiple root elements on VueJS with v-for directive
(6 answers)
Closed 2 years ago.
hopefully someone here will be able to help me with this problem.
I have the following data:
[
{
title: 'Header',
children: [
{
title: 'Paragraph',
children: [],
},
],
},
{
title: 'Container',
children: [
{
title: 'Paragraph',
children: [],
},
],
},
]
I want to render this in a list of <div> like this:
<div class="sortable-item" data-depth="1" data-index="0">Header</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
<div class="sortable-item" data-depth="1" data-index="1">Container</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
I have built a component that would be recursive, this is what I have so far:
<template>
<template v-for="(item, index) in tree">
<div
class="sortable-item"
:data-depth="getDepth()"
:data-index="index"
:key="getKey(index)"
>
{{ item.title }}
</div>
<Multi-Level-Sortable
:tree="item.children"
:parent-depth="getDepth()"
:parent-index="index"
:key="getKey(index + 0.5)"
></Multi-Level-Sortable>
</template>
</template>
<script>
export default {
name: 'MultiLevelSortable',
props: {
tree: {
type: Array,
default() {
return [];
},
},
parentDepth: {
type: Number,
},
parentIndex: {
type: Number,
},
},
methods: {
getDepth() {
return typeof this.parentDepth !== 'undefined' ? this.parentDepth + 1 : 1;
},
getKey(index) {
return typeof this.parentIndex !== 'undefined' ? `${this.parentIndex}.${index}` : `${index}`;
},
},
};
</script>
As you can see not only I have a <template> as the root element I also have a v-for, two "no no" for Vue.js. How can I solve this to render the list of elements like I pointed out above?
Note: I have tried vue-fragment and I was able to achieve the structure I wanted, but then when I tried using Sortable.js it didn't work, as if it wouldn't recognise any of the .sortable-item elements.
Any help will be greatly appreciated! Thank you!
Thanks to #AlexMA I was able to solve my problem by using a functional component. Here is what it looks like:
import SortableItemContent from './SortableItemContent.vue';
export default {
functional: true,
props: {
tree: {
type: Array,
default() {
return [];
},
},
},
render(createElement, { props }) {
const flat = [];
function flatten(data, depth) {
const depthRef = typeof depth !== 'undefined' ? depth + 1 : 0;
data.forEach((item, index) => {
const itemCopy = item;
itemCopy.index = index;
itemCopy.depth = depthRef;
itemCopy.indentation = new Array(depthRef);
flat.push(itemCopy);
if (item.children.length) {
flatten(item.children, depthRef);
}
});
}
flatten(props.tree);
return flat.map((element) => createElement('div', {
attrs: {
'data-index': element.index,
'data-depth': element.depth,
class: 'sortable-item',
},
},
[
createElement(SortableItemContent, {
props: {
title: element.title,
indentation: element.indentation,
},
}),
]));
},
};
The SortableItemContent component looks like this:
<template>
<div class="item-content">
<div
v-for="(item, index) in indentation"
:key="index"
class="item-indentation"
></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">{{ title }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'SortableItemContent',
props: {
title: String,
indentation: Array,
},
};
</script>
Given the data I have posted on my question, it now renders the HTML elements like I wanted:
<div data-index="0" data-depth="0" class="sortable-item">
<div class="item-content">
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Header</div>
</div>
</div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
<div class="item-content">
<div class="item-indentation"></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Paragraph</div>
</div>
</div>
</div>
<div data-index="1" data-depth="0" class="sortable-item">
<div class="item-content">
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Container</div>
</div>
</div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
<div class="item-content">
<div class="item-indentation"></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Paragraph</div>
</div>
</div>
</div>
Thank you again #AlexMA for the tip on Functional Components.