I used the Composition API when migrating the project from vue2.x to vue3.0, but the page does not work properly, in vue3.0
The environment prompts me to have an "Unexpected mutation of "task" prop" error, I want to know how to write the correct compos API
This is Vue2.x code
<template>
<transition name="fade">
<div class="task" v-if="!task.deleted">
<input :id="id" type="checkbox" v-model="task.done" />
<label :for="id">{{ task.title }}</label>
<transition name="fade">
<span
class="task_delete"
v-show="task.done"
#click="deleteTask({ task })"
>
<i class="fa fa-trash"></i>
</span>
</transition>
</div>
</transition>
</template>
<script>
import { mapMutations } from "vuex";
let GID = 1;
export default {
props: {
task: {
type: Object,
required: true,
},
},
data() {
return {
id: `task-${GID++}`,
};
},
methods: {
...mapMutations(["deleteTask"]),
},
};
</script>
<style lang="scss">
.task {
display: flex;
padding: 12px 0;
border-bottom: 1px solid #eee;
font-size: 14px;
}
.task input {
display: none;
}
.task label {
flex: 1;
line-height: 20px;
}
.task label:before,
.task label:after {
content: "";
display: inline-block;
margin-right: 20px;
margin-top: 1px;
width: 14px;
height: 14px;
vertical-align: top;
}
.task label:before {
border: 1px solid #ccc;
border-radius: 2px;
background-color: white;
}
.task label:after {
content: "\f00c";
position: relative;
display: none;
z-index: 10;
margin-right: -16px;
width: 10px;
height: 10px;
padding: 3px;
border-radius: 2px;
font: normal normal normal 10px/1 FontAwesome;
color: white;
background-color: #ccc;
float: left;
}
.task input:checked + label:after {
display: inline-block;
}
.task_delete {
padding: 0 10px;
color: #ccc;
font-size: 16px;
}
.fade-leave-to,
.fade-enter {
opacity: 0;
}
.fade-enter-to,
.fade-leave {
opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.3s ease;
}
</style>
This is Vue3.0 code use Composition API, but not work
<template>
<transition name="fade">
<div class="task" v-if="!data.task.deleted">
<input :id="id" type="checkbox" v-model="data.task.done" />
<label :for="id">{{ data.task.title }}</label>
<transition name="fade">
<span
class="task_delete"
v-show="data.task.done"
#click="deleteTask({ task })"
>
<i class="fa fa-trash"></i>
</span>
</transition>
</div>
</transition>
</template>
<script>
import { reactive } from "vue";
import { mapMutations } from "vuex";
let GID = 1;
export default {
name: "Task",
props: {
task: {
type: Object,
required: true,
},
},
setup(props) {
const data = reactive({
task: props.task,
id: `task-${GID++}`,
});
return { data };
},
methods: {
...mapMutations(["deleteTask"]),
},
};
</script>
<style lang="scss">
.task {
display: flex;
padding: 12px 0;
border-bottom: 1px solid #eee;
font-size: 14px;
}
.task input {
display: none;
}
.task label {
flex: 1;
line-height: 20px;
}
.task label:before,
.task label:after {
content: "";
display: inline-block;
margin-right: 20px;
margin-top: 1px;
width: 14px;
height: 14px;
vertical-align: top;
}
.task label:before {
border: 1px solid #ccc;
border-radius: 2px;
background-color: white;
}
.task label:after {
content: "\f00c";
position: relative;
display: none;
z-index: 10;
margin-right: -16px;
width: 10px;
height: 10px;
padding: 3px;
border-radius: 2px;
font: normal normal normal 10px/1 FontAwesome;
color: white;
background-color: #ccc;
float: left;
}
.task input:checked + label:after {
display: inline-block;
}
.task_delete {
padding: 0 10px;
color: #ccc;
font-size: 16px;
}
.fade-leave-to,
.fade-enter {
opacity: 0;
}
.fade-enter-to,
.fade-leave {
opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.3s ease;
}
</style>
I think the problem is you're using v-model for props on checkbox, it will change props value directly and vue not allow it. Try update props value manual by emit event. And u dont need to use props with reactive in child component, but need to reactive that props in parent component
<input :id="id" type="checkbox" v-value="task.done" #change="updateCheckbox($event)")/>
In script:
export default {
name: "Task",
props: {
task: {
type: Object,
required: true,
},
},
emits: ['updateCheckbox'],
setup(props) {
const data = reactive({
id: `task-${GID++}`,
});
return { data };
},
methods: {
updateCheckbox(e) {
this.$emit('updateCheckbox', e.target.value)
}
},
};
I'm not sure how to set a default/initial selected option using vue-select, specifically when you use slots.
Here is the code: https://vue-select.org/guide/slots.html
I would want one of the options to initially display on page load.
Pass the option object you want as a default value into the v-model prop of the component.
Vue.component('v-select', VueSelect.VueSelect)
new Vue({
el: '#app',
data: {
selectedOption: {
title: 'Read the Docs',
icon: 'fa-book',
url: 'https://codeclimate.com/github/sagalbot/vue-select'
},
options: [
{
title: 'Read the Docs',
icon: 'fa-book',
url: 'https://codeclimate.com/github/sagalbot/vue-select'
},
{
title: 'View on GitHub',
icon: 'fa-github',
url: 'https://codeclimate.com/github/sagalbot/vue-select'
},
{
title: 'View on NPM',
icon: 'fa-database',
url: 'https://codeclimate.com/github/sagalbot/vue-select'
},
{
title: 'View Codepen Examples',
icon: 'fa-pencil',
url: 'https://codeclimate.com/github/sagalbot/vue-select'
}
]
}
})
body {
font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
}
h1,.muted {
color: #2c3e5099;
}
h1 {
font-size: 26px;
font-weight: 600;
text-rendering: optimizelegibility;
-moz-osx-font-smoothing: grayscale;
-moz-text-size-adjust: none;
}
#app {
max-width: 30em;
margin: 1em auto;
}
#app .dropdown li {
border-bottom: 1px solid rgba(112, 128, 144, 0.1)
}
#app .dropdown li:last-child {
border-bottom: none;
}
#app .dropdown li a {
padding: 10px 20px;
display: flex;
width: 100%;
align-items: center;
font-size: 1.25em;
}
#app .dropdown li a .fa {
padding-right: 0.5em;
}
<script src="https://unpkg.com/vue-select#3.2.0/dist/vue-select.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>Vue Select - Custom Option Templating</h1>
<v-select :options="options" v-model="selectedOption" label="title">
<template slot="option" slot-scope="option">
<span class="fa" :class="option.icon"></span>
{{ option.title }}
</template>
</v-select>
</div>
I've been doing some vuejs for almost a year now and I never really got animations and transitions to work, it seems very unclear to me how all this is supposed to work and today I decided to try and finally understand them but once again I am stuck, the transition simply won't work and I don't understand what I'm doing wrong.
Here is my code:
The HTML
<div id="app">
<div class="row">
<div class="container-fluid">
<transition-group name="flip-list" tag="div" class="row horizontal-scroll">
<div class="col-xs-12 col-md-2 col-lg-2 col-card"
v-for="(someCard, index) in someCards" v-bind:key="index">
<div class="some-card" #click="changeOrder(someCard)">
{{ someCard.someTitle }}
</div>
</div>
</transition-group>
</div>
</div>
</div>
The JS
new Vue({
el: "#app",
data: {
someCards: [
{
order: 2,
someTitle: 'Title 1'
},
{
order: 2,
someTitle: 'Title 2'
},
{
order: 2,
someTitle: 'Title 3'
},
{
order: 3,
someTitle: 'Title 4'
},
{
order: 3,
someTitle: 'Title 5'
},
{
order: 1,
someTitle: 'Title 6'
},
{
order: 1,
someTitle: 'Title 7'
},
{
order: 1,
someTitle: 'Title 8'
},
{
order: 3,
someTitle: 'Title 1'
}
]
},
methods: {
changeOrder(someCardData) {
let newRandom = Math.floor(Math.random() * 3) + 1;
while (newRandom === someCardData.order) {
newRandom = Math.floor(Math.random() * 3) + 1;
}
someCardData.order = newRandom;
this.reorderList();
},
reorderList() {
this.someCards.sort(function(objA, objB) {
return objA.order - objB.order;
})
},
}
})
The CSS
/* default */
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
/* bootstrap */
.row {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, .col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, .col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, .col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, .col-xl-auto {
position: relative;
width: 100%;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
}
/* my css */
.horizontal-scroll {
overflow-x: auto;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
}
.col-card {
max-width: 14%;
}
.some-card {
height: 220px;
font-size: 16px;
text-align: center;
padding-right: 5px;
margin-top: 5px;
-webkit-box-shadow: 1px 1px 5px rgba(0,0,0,.1);
box-shadow: 1px 1px 5px rgba(0,0,0,.1);
border-radius: 2px;
background: #fff;
margin-bottom: 20px;
position: relative;
border-top-style: solid;
}
.flip-list-move {
transition: transform 1s;
}
https://jsfiddle.net/eywraw8t/459088/
I am trying to have an animation triggered when I reorder a card in a list of cards aligned in a row. To change the position of a card, simply click on it. The reorder works but no transition is done.
I used this documentation
https://v2.vuejs.org/v2/guide/transitions.html#List-Move-Transitions
and have seen a couple of fairly easy tutorials but I must be missing something. Can someone please check the code out and tell me what it is ?
Thanks
It's because you're using the index as a key. The index will always be the same for the same position even if the card changes. I'd give each card a unique id and then use that as a key instead ..
new Vue({
el: "#app",
data: {
someCards: [{
id: 0,
order: 2,
someTitle: 'Title 1'
},
{
id: 1,
order: 2,
someTitle: 'Title 2'
},
{
id: 2,
order: 2,
someTitle: 'Title 3'
},
{
id: 3,
order: 3,
someTitle: 'Title 4'
},
{
id: 4,
order: 3,
someTitle: 'Title 5'
},
{
id: 5,
order: 1,
someTitle: 'Title 6'
},
{
id: 6,
order: 1,
someTitle: 'Title 7'
},
{
id: 7,
order: 1,
someTitle: 'Title 8'
},
{
id: 8,
order: 3,
someTitle: 'Title 1'
}
]
},
methods: {
changeOrder(someCardData) {
let newRandom = Math.floor(Math.random() * 3) + 1;
while (newRandom === someCardData.order) {
newRandom = Math.floor(Math.random() * 3) + 1;
}
someCardData.order = newRandom;
this.reorderList();
},
reorderList() {
this.someCards.sort(function(objA, objB) {
return objA.order - objB.order;
})
},
}
})
/* default */
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
/* bootstrap */
.row {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
.col-1,
.col-2,
.col-3,
.col-4,
.col-5,
.col-6,
.col-7,
.col-8,
.col-9,
.col-10,
.col-11,
.col-12,
.col,
.col-auto,
.col-sm-1,
.col-sm-2,
.col-sm-3,
.col-sm-4,
.col-sm-5,
.col-sm-6,
.col-sm-7,
.col-sm-8,
.col-sm-9,
.col-sm-10,
.col-sm-11,
.col-sm-12,
.col-sm,
.col-sm-auto,
.col-md-1,
.col-md-2,
.col-md-3,
.col-md-4,
.col-md-5,
.col-md-6,
.col-md-7,
.col-md-8,
.col-md-9,
.col-md-10,
.col-md-11,
.col-md-12,
.col-md,
.col-md-auto,
.col-lg-1,
.col-lg-2,
.col-lg-3,
.col-lg-4,
.col-lg-5,
.col-lg-6,
.col-lg-7,
.col-lg-8,
.col-lg-9,
.col-lg-10,
.col-lg-11,
.col-lg-12,
.col-lg,
.col-lg-auto,
.col-xl-1,
.col-xl-2,
.col-xl-3,
.col-xl-4,
.col-xl-5,
.col-xl-6,
.col-xl-7,
.col-xl-8,
.col-xl-9,
.col-xl-10,
.col-xl-11,
.col-xl-12,
.col-xl,
.col-xl-auto {
position: relative;
width: 100%;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
}
/* my css */
.horizontal-scroll {
overflow-x: auto;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
}
.col-card {
max-width: 14%;
}
.some-card {
height: 220px;
font-size: 16px;
text-align: center;
padding-right: 5px;
margin-top: 5px;
-webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, .1);
box-shadow: 1px 1px 5px rgba(0, 0, 0, .1);
border-radius: 2px;
background: #fff;
margin-bottom: 20px;
position: relative;
border-top-style: solid;
}
.flip-list-move {
transition: transform 1s;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<div class="row">
<div class="container-fluid">
<transition-group name="flip-list" tag="div" class="row horizontal-scroll">
<div class="col-xs-12 col-md-2 col-lg-2 col-card" v-for="(someCard, index) in someCards" v-bind:key="someCard.id">
<div class="some-card" #click="changeOrder(someCard)">
{{ someCard.someTitle }}
</div>
</div>
</transition-group>
</div>
</div>
</div>
JSFiddle
I am creating a full screen navigation
This navigation is opening on a button click. The problem is that the liand close button are not accessible. I am not able to click on them.
Html
<div id="myNav" class="overlay">
<v-btn class="white--text closebtn" icon v-on:click.prevent="CloseDialog">
<v-icon>cancel</v-icon>
</v-btn>
<div class="overlay-content">
About
Services
Clients
Contact
</div>
</div>
Css
.overlay {
height: 100%;
width: 0;
position: fixed;
z-index: 4;
top: 0;
left: 0;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0, 0.9);
overflow-x: hidden;
transition: 0.5s;
}
.overlay-content {
z-index :99;
position: relative;
top: 25%;
width: 100%;
text-align: center;
margin-top: 30px;
}
.overlay a {
padding: 8px;
text-decoration: none;
font-size: 36px;
color: #818181;
display: block;
transition: 0.3s;
}
.overlay a:hover, .overlay a:focus {
color: #f1f1f1;
}
.overlay .closebtn {
position: absolute;
top: 40px;
right: 55px;
font-size: 80px;
cursor :pointer
}
#media screen and (max-height: 450px) {
.overlay a {font-size: 20px}
.overlay .closebtn {
font-size: 40px;
top: 15px;
right: 35px;
}
}
Javscript
<script>
import { mapGetters } from "vuex"
export default {
computed: mapGetters({ isLoggedIn: 'CheckAuth', items: 'GetItems' }),
data() {
return {
clipped: true,
drawer: true,
fixed: false,
miniVariant: true,
right: true,
rightDrawer: false,
title: 'Vuetify.js'
}
},
methods: {
Login() {
this.$store.dispatch('ChangeAuth');
},
OpenDialog() {
document.getElementById("myNav").style.width = "100%";
},
CloseDialog() {
document.getElementById("myNav").style.width = "0%";
}
}
}
</script>
This is a pure CSS issue. You can either add :after pseudo element and create the background with it. Or you can use pointer-events: none;
CSS property on the overlay element.
I try to use Vuejs with jquery. I don't why this is not working. When first loading, icons doesn't seem. I don't know what is wrong. when you click on items, plus and minus icons seen and it is working as expected. But why it is not working in first loading ?
Any help will be appreciated.
var data = {
name: 'My Tree',
children: [{
name: 'hello'
}, {
name: 'wat'
}, {
name: 'child folder',
children: [{
name: 'child folder',
children: [{
name: 'hello'
}, {
name: 'wat'
}]
}, {
name: 'hello'
}, {
name: 'wat'
}, {
name: 'child folder',
children: [{
name: 'hello'
}, {
name: 'wat'
}]
}]
}]
}
// define the item component
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
computed: {
isFolder: function() {
return this.model.children &&
this.model.children.length
}
},
});
// boot up the demo
var demo = new Vue({
el: '#demo',
ready: function() {
},
data: {
treeData: data
}
});
Vue.nextTick(function() {
$('.tree li:has(ul)').addClass('parent_li').find(' > span').attr('title', 'Collapse this branch');
$('.tree li.parent_li > span').on('click', function(e) {
var children = $(this).parent('li.parent_li').find(' > ul > li');
if (children.is(":visible")) {
children.hide('fast');
$(this).attr('title', 'Expand this branch').find(' > i').addClass('icon-plus-sign').removeClass('icon-minus-sign');
} else {
children.show('fast');
$(this).attr('title', 'Collapse this branch').find(' > i').addClass('icon-minus-sign').removeClass('icon-plus-sign');
}
e.stopPropagation();
});
})
.tree {
min-height: 20px;
padding: 19px;
margin-bottom: 20px;
background-color: #fbfbfb;
border: 1px solid #999;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05)
}
.tree li {
list-style-type: none;
margin: 0;
padding: 10px 5px 0 5px;
position: relative
}
.tree li::before,
.tree li::after {
content: '';
left: -20px;
position: absolute;
right: auto
}
.tree li::before {
border-left: 1px solid #999;
bottom: 50px;
height: 100%;
top: 0;
width: 1px
}
.tree li::after {
border-top: 1px solid #999;
height: 20px;
top: 25px;
width: 25px
}
.tree li span {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border: 1px solid #999;
border-radius: 5px;
display: inline-block;
padding: 3px 8px;
text-decoration: none
}
.tree li.parent_li>span {
cursor: pointer
}
.tree>ul>li::before,
.tree>ul>li::after {
border: 0
}
.tree li:last-child::before {
height: 30px
}
.tree li.parent_li>span:hover,
.tree li.parent_li>span:hover+ul li span {
background: #eee;
border: 1px solid #94a0b4;
color: #000
}
<link href="https://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<ul id="demo">
<div class="tree well">
<item :model="treeData"></item>
</div>
</ul>
<script type="text/x-template" id="item-template">
<li>
<span><i v-if="isFolder" class="icon-minus-sign"></i> {{model.name}}</span>
<ul v-if="isFolder">
<item v-for="model in model.children" :model="model"></item>
</ul>
</li>
</script>
Actually it was my fault. I called wrong class name for the icon. I am updating and everything will work fine. I hope this vue recursive tree model will be useful for others.