Vuejs shows the same data on multiple components - vue.js

I have the following component chain in my project (templates are in different vue files, here ... acts as a separator to make it readable):
<!-- Sensors template -->
<div class="container">
<div class= "sensors_item">
<SensorItem v-for="i in sensors" :sensordata="i" :key="sensors.id"></SensorItem>
</div>
</div>
...
<!-- SensorItem template -->
<div>
<SensorParameterItem v-for="i in sensordata.sensorparams" :parameterdata="i" :key="sensordata.sensorparams.id"></SensorParameterItem>
</div>
...
<!-- SensorParameterItem template -->
<div class="col parameter-icon-clickable" v-on:click="openChart">
<i class="fas fa-chart-line" color="white"></i>
</div>
</div>
<ChartCollapsible class="parameter-icon-clickable" :isOpen="isChartOpen" :pdata="parameterdata"/>
</div
...
<!-- ChartCollapsible template-->
<div>
<transition appear name="modal">
<div v-if="isOpen">
<div class="chart-container">
<apexcharts height="400" width="100%" ref="chart" type="area" :options="o1" :series="setSeries"></apexcharts>
<!-- OK -->
</div>
</div>
</transition>
</div>
...
Functions
...
openChart: function() {
let data = {count: 144};
console.log('openChart');
this.$store.dispatch('getsensordata', data)
this.isChartOpen = !this.isChartOpen;
}
...
computed: {
setSeries() {
console.log("Computed.")
if(this.$store.getters.authStatus == "received") {
this.s1 = _.cloneDeep(this.$store.getters.getData);
} else {
this.s1 = _.cloneDeep([{data: [{x:0,y:0}]}]);
}
return this.s1;
}
}
I'm calling the backend by clicking in the SensorParameterItem (openChart fn) to receive chart data. Then, in ChartCollapsible I have computed which verifies that new data is received. After that it deep copies the new data into a property and returns that property to the chart component. I'd expect that each ChartCollapsible component would have its own chart data but it's not: I have 10 ChartCollapsibles rendered and all is updated with the same data, when I click any openchart button.
Any help would be great how to solve this issue!

Okay I've found what caused this behaviour: there is a computed property in the component, which monitors this.$store.getters.authStatus == "received" state. Since ALL component instances monitor this single state, when it changes, ALL component instances will be updated.
A possible fix is to send some component specific data (title, id, etc.) along this.$store.dispatch('getsensordata', data) request which will be replied back to all component instances, but only one component will be updated:
computed: {
setSeries() {
if(this.$store.getters.authStatus == "received") {
// Filter down to our chart otherwise all charts will be updated!
if(this.header.title == this.$store.getters.getData.header.title && this.parameterdata.pidx == this.$store.getters.getData.header.pidx) {
this.s1 = _.cloneDeep(this.$store.getters.getData.data);
}
}
return this.s1;
}
}

Related

How to remove Id using put method

Indexing.vue;
<template>
<div class="row">
<div class="col-md-12">
<card class="card-plain" card-body-classes="table-full-width" header-classes="row">
<template slot="header" :class="row">
<h3 class="card-title">Indexing</h3>
<base-button type="primary" size="sm" #click="addNewIndexing" class="ml-4">Add Indexing</base-button>
</template>
<div class="row indexing-cards-container pl-5">
<div class="col card " v-for="data in indexingList" :key="data.id">
<div class="row">
<p class="pl-4">{{data.indexing_name}}</p>
</div>
<div class="row">
<p class="pl-4">{{data.indexing_url}}</p>
</div>
<div class="row IndexingImage pb-2 ">
<img :src="data.indexing_image_url" class="rounded mx-auto d-block"/>
</div>
<div class="row">
<div class="col pl-4">
<base-switch
class="mt-2"
v-model="data.is_active"
type="primary"
on-text="Active"
off-text="Inactive"
></base-switch>
</div>
<div class="col">
<base-button type="primary" #click="deleteIndexing(data.id)">Delete</base button>
</div>
</div>
</div>
</div>
</card>
<!-- </div>
</div>
</card> -->
</div>
</div>
</template>
<script>
import { Table, TableColumn } from 'element-ui';
import { BaseSwitch } from 'src/components/index';
export default {
components: {
[Table.name]: Table,
[TableColumn.name]: TableColumn,
BaseSwitch
},
data() {
return {
indexingList: [],
};
},
methods: {
addNewIndexing() {
this.$router.push({path: 'addindexing'})
},
deleteIndexing: function() {
this.api.putData('indexing/'+store.state.journalId, ).then((res)=> {
console.log(res.data)
})
},
getIndexingList: function() {
this.api.getDataModule('indexing/'+store.state.journalId, 'journals/v1/').then((res) => {
console.log(res.data)
this.indexingList = res.data.indexingList
}, (err) => {
console.log(err)
})
},
},
mounted: function() {
this.getIndexingList()
}
};
api.js;
putData (action, data) {
let url = `${BASE_URL}`
url += action + '.json'
return instance.put(url, data)
},
Issue :
We have separately created one component called (AddIndexing.vue) to get user details like indexing_name, indexing_url and indexing_image_url. And we wrote post method to post those user datas to the server. That is working fine. In this component (Indexing.vue) I'm showing those received datas from the server, that is also working fine.
I want to implement the delete function in the deleteIndexing method. We wrote the complete API code in api.js file. But I have mentioned only putData function because my boss told me to implement the delete option by putData api function. So now for action parameter in the putData function I passed "indexing/'+store.state.journalId" as an argument through deleteIndexing function and for data parameter in the putData function
I don't know what to pass as an argument. My boss told me to create a temporary object in the name of indexing_id and he asked me to pass as an second argument because indexing_id is the name of the id for each index in the database. But I don't know how to implement exactly. If I click delete option then that button should delete one index data based on the ID.
This is what I need to do. I can post the data and I can get the data but I don't know how to delete. After filling all the fields user entered the submit option means that data will send to the server, and it will create a new index module to have that data for single customer. If we are filling two customer details it means it will create two indexes separately and those two customer details will show in the "indexing.vue" component.
So now by using delete option I want to delete any user details by the ID.' API code everything was working fine but I don't know how to implement. The I do have one toggle button for active state and inactive state for that also I have to write a code by using putData api function.
How to implement this so I can do based on that.
I want to know how to do active and inactive toggle button function also. I tried some ways but that doesn't work for me. I'm quite beginner here so that I'm struggling a lot to do these API's.

Which is the best approach to write a reusable component / customElement in aurelia?

I need to create multiple tables of same html structure but data and columns are different for each table. So I want to create one table component which has common html structure and can get columns and data as parameter , I'm not sure which is the best approach in aurelia to do the same.
app.html
<div class="wrapper">
<div class="main-container">
<header>HEADING</header>
<compose view-model="./table-component" model.bind="items" inherit-binding-context></compose>
</div>
</div>
another-table.html
<div class="wrapper">
<div class="main-container">
<header>HEADING 2</header>
<compose view-model="./table-component" model.bind="items1" inherit-binding-context></compose>
</div>
</div>
table-component.js
export class TableComponent {
constructor() {
this.message = 'Hello world';
}
activate(model) {
this.items = model;
}
getMoreFn(){
this.parent.getMore(); // calling respective component api function
}
bind(bindingContext, overrideContext) {
this.parent = overrideContext.parentOverrideContext.bindingContext;
}
}
table-component.html
<template>
<header id="level-one-head" class="level-one-header">
<div></div>
<div>No.</div>
<div>Name</div>
<div>Type</div>
</header>
<div class="level-data-section ">
<div
repeat.for="item of items"
infinite-scroll-next="getMoreFn"
>
<div class="row">
<div>${$index}</div>
<div>${item}</div>
</div>
</div>
</div>
</template>
Using compose like above and passing the data is right approach or is there any better way to handle dynamic data ?
Planning to pass the column headers also in compose depending on the table

Only show slot if it has content

Is there a way to only display a slot if it has any content?
For example, I'm building a simple Card.vue component, and I only want the footer displayed if the footer slot has content:
Template
<template>
<div class="panel" :class="panelType">
<div class="panel-heading">
<h3 class="panel-title">
<slot name="title">
Default Title
</slot>
</h3>
</div>
<div class="panel-body">
<slot name="body"></slot>
<p class="category">
<slot name="category"></slot>
</p>
</div>
<div class="panel-footer" v-if="hasFooterSlot">
<slot name="footer"></slot>
</div>
</div>
</template>
Script
<script>
export default {
props: {
active: true,
type: {
type: String,
default: 'default',
},
},
computed: {
panelType() {
return `panel-${this.type}`;
},
hasFooterSlot() {
return this.$slots['footer']
}
}
}
</script>
In in View:
<card type="success"></card>
Since the above component doesn't contain a footer, it should not be rendered, but it is.
I've tried using this.$slots['footer'], but this returns undefined.
Does anyone have any tips?
It should be available at
this.$slots.footer
So, this should work.
hasFooterSlot() {
return !!this.$slots.footer;
}
Example.
You should check vm.$slots and also vm.$scopedSlots for it.
hasSlot (name = 'default') {
return !!this.$slots[ name ] || !!this.$scopedSlots[ name ];
}
CSS simplifies this a lot. Just use the following code and voila!
.panel-footer:empty {
display: none;
}
This is the solution for Vue 3 composition API:
<template>
<div class="md:grid md:grid-cols-5 md:gap-6">
<!-- Here, you hide the wrapper if there is no used slot or empty -->
<div class="md:col-span-2" v-if="hasTitle">
<slot name="title"></slot>
</div>
<div class="mt-5 md:mt-0"
:class="{'md:col-span-3': hasTitle, 'md:col-span-5': !hasTitle}">
<div class="bg-white rounded-md shadow">
<div class="py-7">
<slot></slot>
</div>
</div>
</div>
</div>
</template>
<script>
import {ref} from "vue";
export default {
setup(props, {slots}) {
const hasTitle = ref(false)
// Check if the slot exists by name and has content.
// It returns an empty array if it's empty.
if (slots.title && slots.title().length) {
hasTitle.value = true
}
return {
hasTitle
}
}
}
</script>
Now, in Vue3 composition API , you can use useSlots.
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
</script>
<template>
<div v-if="slots.content" class="classname">
<slot name="content"></slot>
</div>
</template>
In short do this in inline:
<template lang="pug">
div
h2(v-if="$slots.title")
slot(name="title")
h3(v-if="$slots['sub-title']")
slot(name="sub-title")
</template>
I have ran into a similiar issue but across a wide code base and when creating atomic design structured components it can be tiring writing hasSlot() methods all the time and when it comes to TDD - its one more method to test... Saying that, you can always put the raw logic in a v-if but i have found that the template end up cluttered and harder to read on occasions especially for a new dev checking out the code structure.
I was tasked to find out a way of removing parent divs of slots when the slot isnt provided.
Issue:
<template>
<div>
<div class="hello">
<slot name="foo" />
</div>
<div class="world">
<slot name="bar" />
</div>
</div>
</template>
//instantiation
<my-component>
<span slot="foo">show me</span>
</my-component>
//renders
<div>
<div class="hello">
<span slot="foo">show me</span>
</div>
<div class="world"></div>
</div>
as you can see, the issue is that i have an almost 'trailing' div, that could provide styling issues when the component author decides there is no need for a bar slot.
ofcourse we could go <div v-if="$slots.bar">...</div> or <div v-if="hasBar()">...</div> etc but like i said - that can get tiresome and eventually end up harder to read.
Solution
My solution was to make a generic slot component that just rendered out a slot with a surrounding div...see below.
//slot component
<template>
<div v-if="!!$slots.default">
<slot />
</div>
</template>
//usage within <my-component/>
<template>
<div>
<slot-component class="hello">
<slot name="foo"/>
</slot-component>
<slot-component class="world">
<slot name="bar"/>
</slot-component>
</div>
</template>
//instantiation
<my-component>
<span slot="foo">show me</span>
</my-component>
//renders
<div>
<div class="hello">
<span>show me</span>
</div>
</div>
I came into use-case issues when trying this idea and sometimes it was my markup structure that needed to change for the benefit of this approach.
This approach reduces the need for small slot checks within each component template. i suppose you could see the component as a <conditional-div /> component...
It is also worth noting that applying attributes to the slot-component instantiation (<slot-component class="myClass" data-random="randomshjhsa" />) is fine as the attributes trickle into the containing div of the slot-component template.
Hope this helps.
UPDATE
I wrote a plugin for this so the need for importing the custom-slot component in each consumer component is not needed anymore and you will only have to write Vue.use(SlotPlugin) in your main.js instantiation. (see below)
const SLOT_COMPONENT = {
name: 'custom-slot',
template: `
<div v-if="$slots.default">
<slot />
</div>
`
}
const SLOT_PLUGIN = {
install (Vue) {
Vue.component(SLOT_COMPONENT.name, SLOT_COMPONENT)
}
}
export default SLOT_PLUGIN
//main.js
import SlotPlugin from 'path/to/plugin'
Vue.use(SlotPlugin)
//...rest of code
Initially I thought https://stackoverflow.com/a/50096300/752916 was working, but I had to expand on it a bit since $scopeSlots returns a function which is always truthy regardless of its return value. This is my solution, though I've come to the conclusion that the real answer to this question is "doing this is an antipattern and you should avoid it if possible". E.g. just make a separate footer component that could be slotted in.
Hacky solution
hasFooterSlot() {
const ss = this.$scopedSlots;
const footerNodes = ss && ss.footer && ss.footer();
return footerNodes && footerNodes.length;
}
Best Practice (helper component for footer)
const panelComponent = {
template: `
<div class="nice-panel">
<div class="nice-panel-content">
<!-- Slot for main content -->
<slot />
</div>
<!-- Slot for optional footer -->
<slot name="footer"></slot>
</div>
`
}
const footerComponent = {
template: `
<div class="nice-panel-footer">
<slot />
</div>
`
}
var app = new Vue({
el: '#app',
components: {
panelComponent,
footerComponent
},
data() {
return {
name: 'Vue'
}
}
})
.nice-panel {
max-width: 200px;
border: 1px solid lightgray;
}
.nice-panel-content {
padding: 30px;
}
.nice-panel-footer {
background-color: lightgray;
padding: 5px 30px;
text-align: center;
}
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<div id="app">
<h1>Panel with footer</h1>
<panel-component>
lorem ipsum
<template #footer>
<footer-component> Some Footer Content</footer-component>
</template>
</panel-component>
<h1>Panel without footer</h1>
<panel-component>
lorem ipsum
</panel-component>
</div>
Hope I understand this right. Why not using a <template> tag, which is not rendered, if the slot is empty.
<slot name="foo"></slot>
Use it like this:
<template slot="foo">
...
</template>
For Vue 3:
Create an utility function
//utils.js
function isSlotHasContent(slotName, slots) {
return Boolean(!!slots[slotName] && slots[slotName]()[0].children.length > 0);
}
In your component:
<script setup>
import { isSlotHasContent } from 'path/to/utils.js';
const slots = useSlots();
// "computed" props has a better performance
const isFooSlotHasContent = computed(() => isSlotHasContent('foo', slots));
</script>
<template>
<div>
<div v-if="isFooSlotHasContent">
<slot name="foo" />
</div>
<div v-if="!isFooSlotHasContent">
Some placeholder
</div>
</div>
</template>
TESTED
So this work for me in vue 3:
I use onMounted to first get the value, and then onUpdate so the value can update.
<template>
<div v-if="content" class="w-1/2">
<slot name="content"></slot>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUpdated } from "vue";
export default defineComponent({
setup(props, { slots }) {
const content = ref()
onMounted(() => {
if (slots.content && slots.content().length) {
content.value = true
}
})
onUpdated(() => {
content.value = slots.content().length
console.log('CHECK VALUE', content.value)
})
})
</script>
#Bert answer does not seem to work for dynamic templates like <template v-slot:foo="{data}"> ... </template>.
i ended up using:
return (
Boolean(this.$slots.foo) ||
Boolean(typeof this.$scopedSlots.foo == 'function')
);
I like the Solution of #AlexMA however in my case I needed to pass props to the function in order to get the nodes to show up.
Here is an example of how I am passing the "row" to the scoped slot, in my case the row contains a type param that I want to test against in the calling component.
<other-component>
<template v-slot:expand="{ row }" v-if="!survey.editable">
<div v-if="row.type != 1" class="flex">
{{ row }}
</div>
</template>
</other-component>
In "other-component" I have the template defined as
<template>
<div>
<div v-for="(row, index) in rows">
{{ hasSlotContent(row) }}
<slot name="expand" :row="row"> </slot>
</div>
</div>
</template>
Because the v-slot requires "row" to be passed to it I created a a method
methods:{
hasSlotContent(row){
const ss = this.$scopedSlots
const nodes = ss && ss.expand && ss.expand({ row: row })
return !!(nodes && nodes.length)
}
}
I call this on each iteration so that it can evaluate itself and give back the appropriate response.
you can use the "hasSlotContent(row)" method where-ever you need it, in my example I'm just outputting the truthy value to the DOM.
I hope this helps someone come to a quicker solution.
Reposting a Vue 3 solution from Github, which also works with Options API, since there was a fairly upvoted method from an Issue there:
The comment itself: https://github.com/vuejs/core/issues/4733#issuecomment-1024816095
The function (remove types if you're not writing TypeScript):
import {
Comment,
Text,
Slot,
VNode,
} from 'vue';
export function hasSlotContent(slot: Slot|undefined, slotProps = {}): boolean {
if (!slot) return false;
return slot(slotProps).some((vnode: VNode) => {
if (vnode.type === Comment) return false;
if (Array.isArray(vnode.children) && !vnode.children.length) return false;
return (
vnode.type !== Text
|| (typeof vnode.children === 'string' && vnode.children.trim() !== '')
);
});
}
This works just as fine, if you delete the slotProps argument (unless you need it).

Presentation Component in Vue2

I want to display all my forms and info pages in floating sidebox.
I don't want to copy and paste the floating sidebox html to all the places. So I want to create a component which acts as container to my forms or info pages.
This is the sample code for form.
<div class="floating-sidebox">
<div class="sidebox-header">
<div class="sidebox-center">
<h3 class="title">{{ title }}</h3>
</div>
</div>
<div class="sidebox-content">
<div class="sidebox-center">
<!-- This is the actual content. Above container code is common for all forms. -->
<vue-form-generator :schema="schema" :model="model" :options="{}"></vue-form-generator>
</div>
</div>
<div class="floating-sidebox-close" #click="cancel"></div>
</div>
<div class="floating-sidebox-overlay"></div>
In above code, I uses vue-form-generator to generate the form. The floating-sidebox elements are common for all forms and info pages. I want to abstract it by Presentational component.
How could I do it Vue2?
Define a component that wraps all your "floating-sidebox" components. You can access the "floating-sideboxes" via this.$children and use their title etc. as navigation placeholder. Since $children is an array you can easily represent the currently visible entity with and index
...
data: function() {
sideboxes: [],
currentIndex: null
},
...
mounted: function() {
this.sideboxes = this.$children;
this.currentIndex= this.$children.length > 0 ? 0 : null;
},
...
computed: {
current: function() {
return this.currentIndex ? this.sideboxes[this.currentIndex] : null;
}
}
You can then bind in the template of the wrapping view
<ul>
<li v-for="(sidebox, index) in sideboxes" #click="currentIndex = index"><!-- bind to prop like title here --></li>
</ul>
<div>
<!-- binding against current -->
</div>
JSfiddle with component https://jsfiddle.net/ptk5ostr/3/

How to perform a transition on a simply component load with Vue.js

This is what the Vue.js documentation state:
Vue provides a transition wrapper component, allowing you to add
entering/leaving transitions for any element or component in the
following contexts:
Conditional rendering (using v-if)
Conditional display (using
v-show)
Dynamic components
Component root nodes
I just simply have a component that is loaded and filled out with XHR data though. How do I go about using a transition to show when the elements v-for gets the data array from an ajax request and builds up my template?
I want a nice fade in instead of simply "plopping" the data into the dom. and have it show up with a delay out of nowhere.
My components example:
https://jsfiddle.net/uwk1x1bx/
<template>
<transition name="fade">
<div class="row">
<div class="col-md-12" v-for="faq in faqs">
<h2>{{ faq.description }}</h2>
<div v-for="item in faq.items" class="panel panel-default">
<div class="panel-heading">{{ item.description }}</div>
<div class="panel-body" v-html="item.answer"></div>
</div><!-- /.panel -->
</div><!-- /.col-md-12 -->
</div><!-- /.row -->
</transition>
</template>
JS
<script>
export default {
name: "Faq",
data() {
return {
faqs: []
}
},
created() {
this.fetchFaqData();
},
methods: {
fetchFaqData() {
Vue.http.get('/services/getfaq').then((response) => {
this.faqs = response.data;
}, (response) => {
console.log(response);
})
}
}
}
</script>