Component inside Component - VueJS - javascript

I am having a hard time to understand this, so I have a component which is already complied which is a grid, now when I click on a button a modal pops-up and display another grid inside the modal at this point my code looks like this for the modal pop-up
<template>
<transition v-if="this.modalVisible" v-bind:title.sync="this.modalVisible" name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
{{ modalHeaderName }}
</div>
<div class="modal-body">
//this is another component
<grid-data :grid-values="dummy" :tool-bar="false"></grid-data>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
import DataTable from './core/gridTable.vue';
export default {
components:{
JqxButton,
'grid-data' : DataTable,
},
props : {
modalHeaderName : String,
modalVisible : Boolean
},
data: function () {
return {
buttonWidth: 120,
buttonHeight: '100%',
value: this.buttonName,
dummy : [
{ name: 'ProductName', type: 'string' },
{ name: 'QuantityPerUnit', type: 'int' },
{ name: 'UnitPrice', type: 'float' },
{ name: 'UnitsInStock', type: 'float' },
{ name: 'Discontinued', type: 'bool' }
],
}
}
}
</script>
Now, the grid is a vue component which was already complied and rendered, now will I import it again it says
[Vue warn]: Failed to mount component: template or render function not defined.
<template>
<div>
<!-- sync here is, getting the value from the updated modal-->
<custom-modal :modal-visible="this.showModal" v-bind:showModal.sync="showModal" :modal-header-name="this.modalHeaderName"></custom-modal>
<JqxGrid :width="width" :source="dataAdapter" :columns="gridValues"
:pageable="true" :autoheight="true" :sortable="true"
:altrows="true" :enabletooltip="true" :editable="true"
:selectionmode="'multiplecellsadvanced'" :showtoolbar="this.toolBar" :rendertoolbar="rendertoolbar">
</JqxGrid>
</div>
</template>
<script>
import JqxGrid from "../jqx-vue/vue_jqxgrid.vue";
import CustomModal from "../customModal";
export default {
components: {
JqxGrid,
'custom-modal' : CustomModal
},
// added the name here
name: 'jqx-grid',
props : {
gridValues : Array,
toolBar : Boolean
},
data: function () {
return {
showModal : false,
modalHeaderName : '',
width: '100%',
dataAdapter: new jqx.dataAdapter({
datatype: 'xml',
datafields : this.gridValues,
url: ''
}),
columns: []
}
},
mounted: function () {
this.createButtons();
},
methods: {
rendertoolbar: function (toolbar) {
let buttonsContainer = document.createElement('div');
buttonsContainer.style.cssText = 'overflow: hidden; position: relative; margin: 5px;';
let addButtonContainer = document.createElement('div');
let deleteButtonContainer = document.createElement('div');
addButtonContainer.id = 'addButton';
deleteButtonContainer.id = 'deleteButton';
addButtonContainer.style.cssText = 'float: left; margin-left: 5px;padding-bottom:25px;';
deleteButtonContainer.style.cssText = 'float: left; margin-left: 5px;padding-bottom:25px;';
buttonsContainer.appendChild(addButtonContainer);
buttonsContainer.appendChild(deleteButtonContainer);
toolbar[0].appendChild(buttonsContainer);
},
createButtons: function () {
let addButtonOptions = {
height: 25, value: ' <i class="fa fa-plus" style="padding-top:3px"></i> Add Items ',
};
let addButton = jqwidgets.createInstance('#addButton', 'jqxButton', addButtonOptions);
let deleteButtonOptions = {
height: 25, value: ' <i class="fa fa-ban" style="padding-top:3px"></i> Remove All ',
};
let deleteButton = jqwidgets.createInstance('#deleteButton', 'jqxButton', deleteButtonOptions);
// add new row.
addButton.addEventHandler('click', (event) => {
this.showModal = true;
this.modalHeaderName = 'Bulk Add Items';
});
// delete selected row.
deleteButton.addEventHandler('click', (event) => {
// alert('delete')
});
},
cellsrenderer: function (row, columnsfield, value, defaulthtml, columnproperties, rowdata) {
if (value < 20) {
return '<span style="margin: 4px; float: ' + columnproperties.cellsalign + '; color: #ff0000;">' + value + '</span>';
}
else {
return '<span style="margin: 4px; float: ' + columnproperties.cellsalign + '; color: #008000;">' + value + '</span>';
}
}
}
}
</script>
How can I overcome this issue?
I have seen question like this which says the component grid is trying to compile again and hence the error but I am not sure of that, so we should be using the complied version of the grid component.
NOTE: Using Vue with Laravel 5.4
Error Image

I didn't see an obvious error when you first posted the code. Right now I see JqxButton inside components of the upper code block, which would be undefined. In your code, you always import some components for which we can't see the code.
Generally, when I'm in a situation like this and everything seems to be looking okay, I remove all sub-components and see if the error goes away. Then, I re-add one component after each other until I hit the error again and try to debug it there.
From your description, I suspect you have some kind of cycle in your dependencies and you might find the documentation about circular references helpful.
Vue needs a lazy import for circular dependencies:
components: {
"my-circular-dependency": () => import("./my-circular-dependency.vue");
}

Related

Use a variable defined in a method inside the template

it's the first time I use Vue (v2 not v3) and I'm stucked trying to use a variable (defined inside a methods) inside the template.
My semplified code:
<template>
<div class="container" #mouseover="isHovered = true" #mouseleave="isHovered = false">
<div class="c-container">
<div ref="topCContainerRef" class="top-c-container">
<div
:class="['top-c', ...]"
:style="{ height: `${isHovered ? 0 : this.scaledHeight}` }" // <-- HERE I need `scaledHeight`
>
</div>
</div>
</div>
</div>
</template>
<script>
import { scaleLinear } from 'd3-scale'
export default {
name: 'MyComponent',
components: { },
props: {
...,
datum: {
type: Number,
required: true,
},
...
},
data: function () {
return {
isHovered: false,
scaledHeight: {},
}
},
mounted() {
this.matchHeight()
},
methods: {
matchHeight() {
const topCContainerHeight = this.$refs.topCContainerRef.clientHeight
const heightScale = scaleLinear([0, 100], [20, topCContainerHeight])
const scaledHeight = heightScale(this.datum)
this.scaledHeight = scaledHeight // I want to use this value inside the template
},
},
}
</script>
How can I get the value of scaledHeight inside the template section?
If I didn't use this, I get no error but the height value is always 0, like scaledHeight is ignored..
I read the documentation but it doesn't help me
I encountered and solved this problem today.
You can change your styles like below.
<div
:class="['top-c', ...]"
:style="{ height: isHovered ? 0 : scaledHeight }"
>
It works fine for me, and hope it will help you~~
Fixed using computed
computed: {
computedHeight: function () {
return this.isHovered ? 0 : this.matchHeight()
},
},
methods: {
matchHeight() {
const topCContainerHeight = this.$refs.topCContainerRef.clientHeight
const heightScale = scaleLinear([0, 100], [20, topCContainerHeight])
return heightScale(this.datum)
},
},

Vuejs 3 not updating menu in primevue

I am new to Vuejs. I am using Primevue library to build the api using the composition vuejs 3.
my problem is that menu is not updating. I want to hide the show button when the element is shown and vice versa. I search all the internet and tried all the solutions I found but in vain.
Any help is appreciated, thank you
export default {
name: "Quote",
components: {
loader: Loader,
"p-breadcrumb": primevue.breadcrumb,
"p-menu": primevue.menu,
"p-button": primevue.button,
},
setup() {
const {
onMounted,
ref,
watch,
} = Vue;
const data = ref(frontEndData);
const quoteIsEdit = ref(false);
const toggle = (event) => {
menu.value.toggle(event);
};
const quote = ref({
display_item_date: true,
display_tax: true,
display_discount: false,
});
const menu = ref();
const items = ref([
{
label: data.value.common_lang.options,
items: [{
visible: quote.value.display_item_date,
label: data.value.common_lang.hide_item_date,
icon: 'pi pi-eye-slash',
command: (event) => {
quote.value.display_item_date = !quote.value.display_item_date;
}
},
{
visible: !quote.value.display_item_date,
label: data.value.common_lang.unhide_item_date,
icon: 'pi pi-eye',
command: () => {
quote.value.display_item_date = !quote.value.display_item_date;
}
}
]
]);
}
return {
data,
quoteIsEdit,
menu,
items,
toggle
};
},
template:
`
<div class="container-fluid" v-cloak>
<div class="text-right">
<p-menu id="overlay_menu" ref="menu" :model="items" :popup="true"></p-menu>
<p-button icon="pi pi-cog" class="p-button-rounded p-button-primary m-2" #click="toggle" aria-haspopup="true" aria-controls="overlay_menu"></p-button>
<p-button :label="data.common_lang.save + ' ' + data.common_lang.quote" class=" m-2" /></p-button>
</div>
</div>
`
};
The problem is the items subproperty change is not reactive, so the items.value.items[].visible props are not automatically updated when quote.value.display_item_date changes.
One solution is to make items a computed prop, so that it gets re-evaluated upon changes to the inner refs:
// const items = ref([...])
const items = computed(() => [...])
demo

vue-js-toggle-button new value not being reflected after database query

Hi guys and thanks in advance for your time on this.
I'm very new to Vue-js and I was wondering if you could help me understand where I'm being bone-headed.
I am trying to use a toggle-button that will activate-deactivate a certain component.
I have done the database implementation stuff i.e. the change is reflected on the database side and other places just fine.
I clearly don't properly understand either the lifecycle or something else but my 'new' toggle value is not being picked up when read from the database: meaning I switch it from 'on' to 'off' and I see it in the database. When I come back to it the toggle is showing 'on'.
I added the code snippet:
<template>
<div class="asset">
<div class="loading" v-if="loading"></div>
<div class="content" v-else>
<b-row>
<b-col cols="12" style="text-align: right;">
<toggle-button :width="70"
:labels="{checked: 'Active', unchecked: 'Inactive'}"
:value="activeStatus"
#change="handleStatusChange"/>
</b-col>
</b-row>
<h2><span>Asset</span> {{ asset.name }}</h2>
...
</div>
</div>
</template>
<script>
import { ToggleButton } from 'vue-js-toggle-button';
export default {
name: "Asset",
components: {
ToggleButton
},
data() {
return {
assetId: 0,
asset: null,
activeStatus: true,
};
},
methods: {
getActiveStatus() {
this.$http.get(`asset/${this.assetId}/status`)
.then((resp) => {
this.activeStatus = resp.bodyText;
<!-- logging for testing only-->
this.$httpError("Retrieved ActiveStatus: " + this.activeStatus);
})
.catch((resp) => {
this.$httpError("Cannot retrieve active status");
});
},
handleStatusChange(event) {
let newStatus = { activeStatus: event.value };
this.$http.post(`asset/${this.assetId}/status`, newStatus).then(() => {
this.activeStatus = newStatus;
}).catch((resp) => {
this.$httpError('Failed to update activeStatus', resp);
});
},
loadAsset() {
this.loading = true;
this.$http
.get(`asset/${this.assetId}`)
.then((resp) => {
this.asset = resp.body;
})
.catch((resp) => {
this.$httpError("Failed to load asset", resp);
})
.finally(() => {
this.loading = false;
});
},
},
created() {
this.assetId = this.$route.params.id;
this.getActiveStatus();
this.loadAsset();
},
};
</script>
<style scoped>
h2 span {
font-size: 12px;
display: block;
text-transform: uppercase;
}
#dbButton,
#dupButton {
width: 30%;
}
#redspan {
color: red;
}
#copyButton,
#pasteButton {
width: 10%;
}
</style>
Just add one line ":sync=true" to the toggle-button component as an attribute.
In your case, the code looks like this:
<toggle-button :width="70"
:labels="{checked: 'Active', unchecked: 'Inactive'}"
:value="activeStatus"
:sync=true
#change="handleStatusChange"/>
Thanks

Vue.js - Computed property not updating - child component

I've created a simple component named DefaultButton.
It bases on properties, that are being set up whenever this component is being created.
The point is that after mounting it, It does not react on changes connected with "defaultbutton", that is an object located in properties
<template>
<button :class="buttonClass" v-if="isActive" #click="$emit('buttonAction', defaultbutton.id)" >
{{ this.defaultbutton.text }}
</button>
<button :class="buttonClass" v-else disabled="disabled">
{{ this.defaultbutton.text }}
</button>
</template>
<script>
export default {
name: "defaultbutton",
props: {
defaultbutton: Object
},
computed: {
buttonClass() {
return `b41ngt ${this.defaultbutton.state}`;
},
isActive() {
return (this.defaultbutton.state === "BUTTON_ACTIVE" || this.defaultbutton.state === "BUTTON_ACTIVE_NOT_CHOSEN");
}
}
};
</script>
Having following component as a parent one:
<template>
<div v-if="state_items.length == 2" class="ui placeholder segment">
{{ this.state_items[0].state }}
{{ this.state_items[1].state }}
{{ this.current_active_state }}
<div class="ui two column very relaxed stackable grid">
<div class="column">
<default-button :defaultbutton="state_items[0]" #buttonAction="changecurrentstate(0)"/>
</div>
<div class="middle aligned column">
<default-button :defaultbutton="state_items[1]" #buttonAction="changecurrentstate(1)"/>
</div>
</div>
<div class="ui vertical divider">
Or
</div>
</div>
</template>
<script type="text/javascript">
import DefaultButton from '../Button/DefaultButton'
export default {
name: 'changestatebox',
data() {
return {
current_active_state: 1
}
},
props: {
state_items: []
},
components: {
DefaultButton
},
methods: {
changecurrentstate: function(index) {
if(this.current_active_state != index) {
this.state_items[this.current_active_state].state = 'BUTTON_ACTIVE_NOT_CHOSEN';
this.state_items[index].state = 'BUTTON_ACTIVE';
this.current_active_state = index;
}
},
},
mounted: function () {
this.state_items[0].state = 'BUTTON_ACTIVE';
this.state_items[1].state = 'BUTTON_ACTIVE_NOT_CHOSEN';
}
}
</script>
It clearly shows, using:
{{ this.state_items[0].state }}
{{ this.state_items[1].state }}
{{ this.current_active_state }}
that the state of these items are being changed, but I am unable to see any results on the generated "DefaultButtons". Classes of objects included in these components are not being changed.
#edit
I've completely changed the way of delivering the data.
Due to this change, I've abandoned the usage of an array; instead I've used two completely not related object.
The result is the same - class of the child component's object is not being
DefaulButton.vue:
<template>
<button :class="buttonClass" v-if="isActive" #click="$emit('buttonAction', defaultbutton.id)" >
{{ this.defaultbutton.text }}
</button>
<button :class="buttonClass" v-else disabled="disabled">
{{ this.defaultbutton.text }}
</button>
</template>
<style lang="scss">
import './DefaultButton.css';
</style>
<script>
export default {
name: "defaultbutton",
props: {
defaultbutton: {
type: Object,
default: () => ({
id: '',
text: '',
state: '',
})
}
},
computed: {
buttonClass() {
return `b41ngt ${this.defaultbutton.state}`;
},
isActive() {
return (this.defaultbutton.state === "BUTTON_ACTIVE" ||
this.defaultbutton.state === "BUTTON_ACTIVE_NOT_CHOSEN");
}
}
};
</script>
ChangeStateBox.vue:
<template>
<div class="ui placeholder segment">
{{ this.state_first.state }}
{{ this.state_second.state }}
{{ this.current_active_state }}
<div class="ui two column very relaxed stackable grid">
<div class="column">
<default-button :defaultbutton="state_first" #buttonAction="changecurrentstate(0)"/>
</div>
<div class="middle aligned column">
<default-button :defaultbutton="state_second" #buttonAction="changecurrentstate(1)"/>
</div>
</div>
<div class="ui vertical divider">
Or
</div>
</div>
</template>
<script type="text/javascript">
import DefaultButton from '../Button/DefaultButton'
export default {
name: 'changestatebox',
data() {
return {
current_active_state: 1
}
},
props: {
state_first: {
type: Object,
default: () => ({
id: '',
text: ''
})
},
state_second: {
type: Object,
default: () => ({
id: '',
text: ''
})
},
},
components: {
DefaultButton
},
methods: {
changecurrentstate: function(index) {
if(this.current_active_state != index) {
if(this.current_active_state == 1){
this.$set(this.state_first, 'state', "BUTTON_ACTIVE_NOT_CHOSEN");
this.$set(this.state_second, 'state', "BUTTON_ACTIVE");
} else {
this.$set(this.state_first, 'state', "BUTTON_ACTIVE");
this.$set(this.state_second, 'state', "BUTTON_ACTIVE_NOT_CHOSEN");
}
this.current_active_state = index;
}
},
},
created: function () {
this.state_first.state = 'BUTTON_ACTIVE';
this.state_second.state = 'BUTTON_ACTIVE_NOT_CHOSEN';
}
}
</script>
You're declaring props wrong. It is either an array of prop names or it is an object with one entry for each prop declaring its type, or it is an object with one entry for each prop declaring multiple properties.
You have
props: {
state_items: []
},
but to supply a default it should be
props: {
state_items: {
type: Array,
default: []
}
},
But your problem is most likely that you're mutating state_items in such a way that Vue can't react to the change
Your main problem is the way you are changing the button state, according with Array change detection vue can't detect mutations by indexing.
Due to limitations in JavaScript, Vue cannot detect the following
changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue When you modify the length of the
array, e.g. vm.items.length = newLength
In case someone will be having the same issue:
#Roy J as well as #DobleL were right.
The reason behind this issue was related with the wrong initialization of state objects.
According to the documentation:
Vue cannot detect property addition or deletion.
Since Vue performs the getter/setter conversion process during instance
initialization, a property must be present in the
data object in order for Vue to convert it and make it reactive.
Before reading this sentence, I used to start with following objects as an initial data:
var local_state_first = {
id: '1',
text: 'Realized',
};
var local_state_second = {
id: '2',
text: 'Active'
};
and the correct version of it looks like this:
var local_state_first = {
id: '1',
text: 'Realized',
state: 'BUTTON_ACTIVE'
};
var local_state_second = {
id: '2',
text: 'Active',
state: 'BUTTON_ACTIVE'
};
whereas declaring the main component as:
<change-state-box :state_first="local_state_first" :state_second="local_state_second" #buttonAction="onbuttonAction"/>
Rest of the code remains the same ( take a look at #edit mark in my main post )

Input field unable to change model when component and model are dynamically created

I have a data structure with nested objects that I want to bind to sub-components, and I'd like these components to edit the data structure directly so that I can save it all from one place. The structure is something like
job = {
id: 1,
uuid: 'a-unique-value',
content_blocks: [
{
id: 5,
uuid: 'some-unique-value',
block_type: 'text',
body: { en: { content: 'Hello' }, fr: { content: 'Bonjour' } }
},
{
id: 9,
uuid: 'some-other-unique-value',
block_type: 'text',
body: { en: { content: 'How are you?' }, fr: { content: 'Comment ça va?' } }
},
]
}
So, I instantiate my sub-components like this
<div v-for="block in job.content_blocks" :key="block.uuid">
<component :data="block" :is="contentTypeToComponentName(block.block_type)" />
</div>
(contentTypeToComponentName goes from text to TextContentBlock, which is the name of the component)
The TextContentBlock goes like this
export default {
props: {
data: {
type: Object,
required: true
}
},
created: function() {
if (!this.data.body) {
this.data.body = {
it: { content: "" },
en: { content: "" }
}
}
}
}
The created() function takes care of adding missing, block-specific data that are unknown to the component adding new content_blocks, for when I want to dynamically add blocks via a special button, which goes like this
addBlock: function(block_type) {
this.job.content_blocks = [...this.job.content_blocks, {
block_type: block_type,
uuid: magic_uuidv4_generator(),
order: this.job.content_blocks.length === 0 ? 1 : _.last(this.job.content_blocks).order + 1
}]
}
The template for TextContentBlock is
<b-tab v-for="l in ['fr', 'en']" :key="`${data.uuid}-${l}`">
<template slot="title">
{{ l.toUpperCase() }} <span class="missing" v-show="!data.body[l] || data.body[l] == ''">(missing)</span>
</template>
<b-form-textarea v-model="data.body[l].content" rows="6" />
<div class="small mt-3">
<code>{{ { block_type: data.block_type, uuid: data.uuid, order: data.order } }}</code>
</div>
</b-tab>
Now, when I load data from the API, I can correctly edit and save the content of these blocks -- which is weird considering that props are supposed to be immutable.
However, when I add new blocks, the textarea above wouldn't let me edit anything. I type stuff into it, and it just deletes it (or, I think, it replaces it with the "previous", or "initial" value). This does not happen when pulling content from the API (say, on page load).
Anyway, this led me to the discovery of immutability, I then created a local copy of the data prop like this
data: function() {
return {
block_data: this.data
}
}
and adjusted every data to be block_data but I get the same behaviour as before.
What exactly am I missing?
As the OP's comments, the root cause should be how to sync textarea value between child and parent component.
The issue the OP met should be caused by parent component always pass same value to the textarea inside the child component, that causes even type in something in the textarea, it still bind the same value which passed from parent component)
As Vue Guide said:
v-model is essentially syntax sugar for updating data on user input
events, plus special care for some edge cases.
The syntax sugar will be like:
the directive=v-model will bind value, then listen input event to make change like v-bind:value="val" v-on:input="val = $event.target.value"
So adjust your codes to like below demo:
for input, textarea HTMLElement, uses v-bind instead of v-model
then uses $emit to popup input event to parent component
In parent component, uses v-model to sync the latest value.
Vue.config.productionTip = false
Vue.component('child', {
template: `<div class="child">
<label>{{value.name}}</label><button #click="changeLabel()">Label +1</button>
<textarea :value="value.body" #input="changeInput($event)"></textarea>
</div>`,
props: ['value'],
methods: {
changeInput: function (ev) {
let newData = Object.assign({}, this.value)
newData.body = ev.target.value
this.$emit('input', newData) //emit whole prop=value object, you can only emit value.body or else based on your design.
// you can comment out `newData.body = ev.target.value`, then you will see the result will be same as the issue you met.
},
changeLabel: function () {
let newData = Object.assign({}, this.value)
newData.name += ' 1'
this.$emit('input', newData)
}
}
});
var vm = new Vue({
el: '#app',
data: () => ({
options: [
{id: 0, name: 'Apple', body: 'Puss in Boots'},
{id: 1, name: 'Banana', body: ''}
]
}),
})
.child {
border: 1px solid green;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<span> Current: {{options}}</span>
<hr>
<div v-for="(item, index) in options" :key="index">
<child v-model="options[index]"></child>
</div>
</div>

Categories