Vuejs - When component is inside in another component methods wont work? - vue.js

When component is inside in another component, methods inside, who calculate discount wont work.
productlist(v-slot="{ cellBox, cellType }", view-type="line")
productshort(title="lorem ipsum", instock="12", caption="EVR07.3405", price="6500", oldprice="7900", image="img/demo/1.jpg", :cellclass="cellBox", :cardtype="cellType")
The percentage() method calculates the percentage of discounts, but when the productshort component is inside productlist component, percentage() method does not execute.
<template lang="pug">
div(:class="cellclass")
.product-short(:class="intclass")
.product-short__footer
.product-short__price
span.smart-badge(v-if="oldprice") – {{ finalsell }} ₽
.fill-price(v-if="price")
b {{ price }} ₽
s(v-if="oldprice") {{ oldprice }} ₽
</template>
<script>
export default {
name: 'productshort',
props: {
sellpercent: {
type: Number
},
cellclass: {
type: String,
default: ''
},
cardtype: {
type: String,
default: 'row'
},
intclass: {
type: String
},
price: {
type: Number
},
oldprice: {
type: Number
},
finalsell: {
type: Number
},
},
methods: {
percentage() {
this.finalsell = parseInt(this.oldprice) - parseInt(this.price);
if(this.oldprice) {
this.sellpercent = parseInt(this.finalsell) / parseInt(this.oldprice) * 100;
this.oldprice = parseInt(this.oldprice).toLocaleString('ru-RU')
}
if(this.finalsell) this.finalsell = parseInt(this.finalsell).toLocaleString('ru-RU');
if(this.price) this.price = parseInt(this.price).toLocaleString('ru-RU');
}
},
mounted() {
this.percentage();
}
}
</script>

Related

Vue.js: Child Component mutates state, parent displays property as undefined

I have a parent component that lists all the tasks:
<template>
<div class="tasks-wrapper">
<div class="tasks-header">
<h4>{{ $t('client.taskListingTitle') }}</h4>
<b-button variant="custom" #click="showAddTaskModal">{{ $t('client.addTask') }}</b-button>
</div>
<b-table
striped
hover
:items="tasks"
:fields="fields"
show-empty
:empty-text="$t('common.noResultsFound')">
</b-table>
<AddTaskModal />
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import AddTaskModal from '#/components/modals/AddTaskModal'
import moment from 'moment'
export default {
name: 'TaskListing',
components: {
AddTaskModal
},
data () {
return {
tasks: [],
fields: [
{ key: 'createdOn', label: this.$t('tasks.tableFields.date'), formatter: 'formatDate' },
{ key: 'domain', label: this.$t('tasks.tableFields.task') },
{ key: 'comment', label: this.$t('tasks.tableFields.comment') },
{ key: 'status', label: this.$t('tasks.tableFields.status') }
]
}
},
computed: {
...mapGetters('users', ['user'])
},
methods: {
...mapActions('tasks', ['fetchTasks']),
...mapActions('users', ['fetchUserById']),
formatDate: function (date) {
return moment.utc(date).local().format('DD.MM.YYYY HH:mm')
},
showAddTaskModal () {
this.$bvModal.show('addTaskModal')
}
},
async mounted () {
const currUserId = this.$router.history.current.params.id
if (this.user || this.user.userId !== currUserId) {
await this.fetchUserById(currUserId)
}
if (this.user.clientNumber !== null) {
const filters = { clientReferenceNumber: { value: this.user.clientNumber } }
this.tasks = await this.fetchTasks({ filters })
}
}
}
</script>
Inside this component there is a child which adds a task modal.
<template>
<b-modal
id="addTaskModal"
:title="$t('modals.addTask.title')"
hide-footer
#show="resetModal"
#hidden="resetModal"
>
<form ref="form" #submit.stop.prevent="handleSubmit">
<b-form-group
:invalid-feedback="$t('modals.requiredFields')">
<b-form-select
id="task-type-select"
:options="taskTypesOptions"
:state="taskTypeState"
v-model="taskType"
required
></b-form-select>
<b-form-textarea
id="add-task-input"
:placeholder="$t('modals.enterComment')"
rows="3"
max-rows="6"
v-model="comment"
:state="commentState"
required />
</b-form-group>
<b-button-group class="float-right">
<b-button variant="danger" #click="$bvModal.hide('addTaskModal')">{{ $t('common.cancel') }}</b-button>
<b-button #click="addTask">{{ $t('modals.addTask.sendMail') }}</b-button>
</b-button-group>
</form>
</b-modal>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'AddTaskModal',
data () {
return {
comment: '',
commentState: null,
taskTypesOptions: [
{ value: null, text: this.$t('modals.addTask.taskType') },
{ value: 'OnBoarding', text: 'Onboarding' },
{ value: 'Accounts', text: 'Accounts' },
{ value: 'TopUp', text: 'Topup' },
{ value: 'Overdraft', text: 'Overdraft' },
{ value: 'Aml', text: 'Aml' },
{ value: 'Transfers', text: 'Transfers' },
{ value: 'Consultation', text: 'Consultation' },
{ value: 'TechnicalSupport', text: 'TechnicalSupport' },
{ value: 'UnblockPin', text: 'UnblockPin' },
{ value: 'Other', text: 'Other' }
],
taskType: null,
taskTypeState: null
}
},
computed: {
...mapGetters('users', ['user']),
...mapGetters('tasks', ['tasks'])
},
methods: {
...mapActions('tasks', ['addNewTask', 'fetchTasks']),
...mapActions('users', ['fetchUserById']),
async addTask (bvModalEvt) {
bvModalEvt.preventDefault()
if (!this.checkFormValidity()) { return }
const currUserId = this.$router.history.current.params.id
if (this.user || this.user.userId !== currUserId) {
await this.fetchUserById(currUserId)
}
const data = {
clientPhone: this.user.phoneNumber,
comment: this.comment,
clientReferenceNumber: this.user.clientNumber,
domain: this.taskType
}
await this.addNewTask(data)
if (this.user.clientNumber !== null) {
const filters = { clientReferenceNumber: { value: this.user.clientNumber } }
this.tasks = await this.fetchTasks({ filters })
// this.tasks may be useless here
}
console.log(this.tasks)
this.$nextTick(() => { this.$bvModal.hide('addTaskModal') })
},
checkFormValidity () {
const valid = this.$refs.form.checkValidity()
this.commentState = valid
this.taskTypeState = valid
return valid
},
resetModal () {
this.comment = ''
this.commentState = null
this.taskTypeState = null
}
}
}
</script>
When I add a task I call getalltasks to mutate the store so all the tasks are added. Then I want to render them. They are rendered but the property createdOn on the last task is InvalidDate and when I console log it is undefined.
The reason I need to call gettasks again in the modal is that the response on adding a task does not return the property createdOn. I do not want to set it on the front-end, I want to get it from the database.
I logged the store and all the tasks are added to the store.
Why is my parent component not rendering this particular createdOn property?
If I refresh the page everything is rendering fine.
If you add anything into a list of items that are displayed by v-for, you have to set a unique key. Based on your explanation, I assume that your key is the index and when you add a new item, you mess with the current indexes. Keys must be unique and unmutateable. What you need to do is to create a unique id for each element.
{
id: Math.floor(Math.random() * 10000000)
}
When you create a new task, use the same code to generate a new id, and use id as key. If this doesn't help, share your d-table and related vuex code too.

Unable to validate a child component from a parent component

I have a selectbox component. I want to reused it in other components.I'm able to reused the selectbox component in other components by adding v-model directive on custom input component but unable to validate selectbox component from other components by using vuetify validation rules.
Test.vue
<v-form ref="form" class="mx-4" v-model="valid">
<Selectbox :name="ward_no" :validationRule="required()" v-model="test.ward_no"/>
<v-btn primary v-on:click="save" class="primary">Submit</v-btn>
export default {
data() {
return {
test: {
ward_no: ''
},
valid: false,
required(propertyType) {
return v => (v && v.length > 0) || `${propertyType} is required`;
},
};
}
Selectbox.vue
<select #change="$emit('input', $event.target.value)" >
<option
v-for="opt in options"
:key="opt.value"
:value="opt.value"
:selected="value === opt.value"
>
{{errorMessage}}
{{ opt.label || "No label" }}
</option>
</select>
export default {
props: {
label: {
type: String,
required: true
},
validationRule: {
type: String,
default: 'text',
validate: (val) => {
// we only cover these types in our input components.
return['requred'].indexOf(val) !== -1;
}
},
name: {
type: String
},
value: {
type: String,
required: true
}
},
data() {
return {
errorMessage: '',
option: "lorem",
options: [
{ label: "lorem", value: "lorem" },
{ label: "ipsum", value: "ipsum" }
]
};
},
methods:{
checkValidationRule(){
if(this.validationRule!="")
{
return this.validationRule.split('|').filter(function(item) {
return item();
})
// this.errorMessage!= ""?this.errorMessage + " | ":"";
}
},
required() {
this.errorMessage!= ""?this.errorMessage + " | ":"";
this.errorMessage += name+" is required";
},
}
};

How to render dynamic the quantity of svg shape element in Vue.js?

My svg shape elements and they attributes was query from server side then
how to render that dynamically in vue to use v-for without static html tag?
Sounds like a job for a Render Function. For example:
Create SvgElement.vue:
render: function (createElement) {
return createElement(
this.shapeType,
{
attrs: this.attrObj
},
this.$slots.default
)
},
props: {
shapeType: {
type: String,
required: true
},
attrString: {
type: string
}
},
computed: {
attrObj() {
// Convert this.attrString into an object
// eg return { cx: 50, cy: 50, r: 10, fill: 'red' }
}
}
Then use in MyComponent.vue
<template>
<svg width="400" height="400">
<SvgElement
v-for="svg in svgArray"
:key="svg.key"
:shapeType="svg.type"
:attrString="svg.attr"
/>
</svg>
</template>
<script>
import SvgElement from './SvgElement'
export default {
components: {
SvgElement
},
data () {
return {
svgArray: [
{ type: 'rect', attr: 'attrString', key: 'shape1' },
{ type: 'circle', attr: 'attrString', key: 'shape2' }
]
}
}
}
</script>

Establishing initial value in a vue subcomponent

I have created a vue subcomponent to select an element from a list:
Vue.component('select-value-from-list', {
template: `
<select v-model="currentValue">
<option v-for='op in options' :value='op.value'>
{{op.label}}
</option>
</select>
`,
props: {
initialValue: {
type: String
},
options: {
type: Array,
required: true
}
},
data: function() {
return {
currentValue: "Indefinido"
}
},
watch: {
currentValue: function () {
this.$emit('value-change', this.currentValue);
}
},
created: function() {
if(this.initialValue)
this.currentValue = this.initialValue;
}
})
And I want the menu with options to appear with the option corresponding to the initialValue selected. How should I do this?

The use of bidirectional binding of components in Vue.js

I'm a new learner of Vue.js and trying to implement the example (example of currency filter) on the official guideline.
However, when implementing, I rename the property of the component (value) to (priceValue). After the change, the input box cannot format the value - it always shows '0' instead of the formatted value.
It is the only change I made. What is the problem?
Vue.component('currency-input', {
template: '\
<div>\
<label v-if="label">{{ label }}</label>\
$\
<input\
ref="input"\
v-bind:value="priceValue"\
v-on:input="updateValue($event.target.value)"\
v-on:focus="selectAll"\
v-on:blur="formatValue"\
>\
</div>\
',
props: {
priceValue: {
type: Number,
default: 0
},
label: {
type: String,
default: ''
}
},
mounted: function () {
this.formatValue()
},
methods: {
updateValue: function (value) {
var result = currencyValidator.parse(value, this.priceValue)
if (result.warning) {
this.$refs.input.value = result.value
}
this.$emit('input', result.value)
},
formatValue: function () {
// console log here always get 0
this.$refs.input.value = currencyValidator.format(this.priceValue)
},
selectAll: function (event) {
setTimeout(function () {
event.target.select()
}, 0)
}
}
})
new Vue({
el: '#app',
data: {
price: 0,
shipping: 0,
handling: 0,
discount: 0
},
computed: {
total: function () {
return ((
this.price * 100 +
this.shipping * 100 +
this.handling * 100 -
this.discount * 100
) / 100).toFixed(2)
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdn.rawgit.com/chrisvfritz/5f0a639590d6e648933416f90ba7ae4e/raw/98739fb8ac6779cb2da11aaa9ab6032e52f3be00/currency-validator.js"></script>
<div id="app">
<currency-input
label="Price"
v-model="price"
></currency-input>
<currency-input
label="Shipping"
v-model="shipping"
></currency-input>
<currency-input
label="Handling"
v-model="handling"
></currency-input>
<currency-input
label="Discount"
v-model="discount"
></currency-input>
<p>Total: ${{ total }}</p>
</div>
According to DOCS:
For a component to work with v-model, it should (these can be
configured in 2.2.0+):
accept a value prop
emit an input event with the new value
This can be configured sinse 2.2.x with a model options block:
Vue.component('currency-input', {
model: {
prop: 'propValue',
// event: 'input' - you can also customize event name, not needed in your case
},
With this in place, your code will work again: https://jsfiddle.net/wostex/j3a616a5/