A WAY TO ACCESS DATA FROM ORIGINAL/BASE COMPONENT IN EXTENDED COMPONENT DATA()
A WAY TO MERGE IN VUE 3 LIKE VUE 2
If are you looking for a way to extends/mixins your code to add some functionality to your component(tag) organizing then, codding less and creating a beautiful clean legible vue structure but maybe you need to know the differences between vue 2 and 3 and helping-us to find some these anwers
EXEMPLE CODE
(Ex: Caller)
<!-- APP.VUE -->
<template>
<div id="app">
<h2>Original/base 'tag': <original message="Test String" /></h2>
<hr>
<h2>EXTENDED 'tag': <EXTENDoriginal message="Have this too" extra="THIS OTHER PROP" /></h2>
</div>
</template>
<script>
import BaseComponent from "./components/BaseComponent.vue"
import ExtendedComponent from "./components/ExtendedComponent.vue"
export default {
components: {
original: BaseComponent,
EXTENDoriginal: ExtendedComponent,
}
}
</script>
(Ex: Original/Base component)
<!-- BaseComponent.vue = Option API Component -->
<template>
<div style="color:red;">
<div>Original component: {{ message }}</div>
<div>{{ user }}</div>
</div>
</template>
<script>
export default {
name: "original",
props: {
message: {
type: String, required: false
}
},
data() {
return {
primitive: 1,
user: {
name: 'Jack', id: 1111
}
}
},
methods: {
ORIGINAL_FUNCTION(val) {
alert(val)
}
}
}
</script>
(Ex: Extended component from Original/Base)
<!-- ExtendedComponent.vue = Option API Component EXTENDED -->
<template>
<div style="color:blue;">
<div>EXTENDED component: <u>{{ message }}</u></div>
<div>plus <u>{{ extra }}</u></div>
<div>user: {{ user }}</div>
<h4 #click="exampleFuncExtended">Click here to call "original" function from "local" function</h4>
</div>
</template>
<script>
import Original from "./BaseComponent.vue"
export default {
name: "EXTENDoriginal",
extends: Original,
props: {
extra: {
type: String, required: false
}
},
data() {
let primitiveExtended = this.primitive + 99
return {
primitiveExtended,
user: {
id: 9999
}
}
},
methods: {
exampleFuncExtended() {
alert(this.primitive + '(ok) -- ' + this.primitiveExtended + ' (does not work)')
this.ORIGINAL_FUNCTION("hello it's me you're looking for - A call to original Sample")
}
}
}
</script>
MUST KNOW
VUE 2.5.2 will return ( try/test https://codesandbox.io/s/vue2-extends-example-q5rcpk )
Original/base 'tag':
Original component: Test String
{ "name": "Jack", "id": 1111 }
------------------------------------
EXTENDED 'tag':
EXTENDED component: Have this too
plus THIS OTHER PROP and NaN
user: { "id": 9999, "name": "Jack" } <------ VUE 2 MERGE
VUE 3.0.2 will return ( try/test https://codesandbox.io/s/vue3-extends-example-o50lh1 )
Original/base 'tag':
Original component: Test String
{ "name": "Jack", "id": 1111 }
------------------------------------
EXTENDED 'tag':
EXTENDED component: Have this too
plus THIS OTHER PROP and NaN
user: { "id": 9999 } <------ VUE 3 MERGE
1 - you can find some information about this at https://v3-migration.vuejs.org/breaking-changes/data-option.html and a mention "Migration build flag: OPTIONS_DATA_FN" but I'm looking for a way to merge like VUE 2 just into one simple component/page not all project
2 - the "NaN" it's a start to ask: Is there a way to read data from original/base component into data() section of extended component? Because they are accessible in and methods: like example code above do
Related
I am migrating things from Vue.js 2 to Vue.js 3. During my migration, I just mentioned that eslint does warn me, because I am mutating props.
This is an example of an element that causes this:
<template>
<ToggleField
v-model="item.checked"
:name="`item.${name}.checked`"/>
</template>
<script>
import ToggleField from "./ToggleField";
export default {
name: 'TestField',
components: {ToggleField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
This element is deeply nested and every parent element passes the :item-attribute to the next "level" until it's finally displayed and changeable due v-model.
This is an example:
Parent view
<template>
<CustomForm name="test" :item="item" />
<!-- Reflect changes on item here -->
{{ item }}
</template>
<script>
import CustomForm from "./CustomForm";
export default {
components: {
CustomForm
},
data: () => ({
item:
{name: 'Foo', 'checked': false},
}),
}
</script>
CustomForm
<template>
<!-- Do other fancy stuff here -->
<TestField :name="name" :item="item"/>
</template>
<script>
import TestField from "./TestField";
export default {
name: 'CustomForm',
components: {TestField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
TestField
<template>
<ToggleField
v-model="item.checked"
:name="`item.${name}.checked`"/>
</template>
<script>
import ToggleField from "./ToggleField";
export default {
name: 'TestField',
components: {ToggleField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
So my question is: How can I update the item and reflect the changes to it's parent (and it's parent, and it's parent again, if necessary) without running into the prop-mutation?
I am new to Vue and studied the other questions here and can not seem to work out how to pass an object to a child component and reference individual elements of that object. I want all elements of the object to be able to be individually accessed and used in the footer template where appropriate.
I am using NPM from node-js and run the dev with npm run dev
In my root component, App.Vue, I have as follows: (unrelated lines omitted)
<template>
<div>
<router-view></router-view>
<bbay-footer v-bind:footerData="footerData"></bbay-footer>
</div>
</template>
<script>
import Footer from './Components/SiteWide/Footer.vue'
export default {
components: {
'bbay-footer': Footer,
},
data() {
return {
footerData: [{
titleFooter: 'My Computer Support',
mainNumber: '0411111111',
otherNumber: '0400000000',
emailUs: 'mailto:info#mycomputersupport.com'
}]
}
}
}
</script>
Now in Footer.vue I have:
<template>
<footer>
<p>{{startCR}} - {{copyright}} </p>
</footer>
</template>
<script>
export default {
props: {
titleFooter: String,
mainNumber: Number,
otherNumber: Number,
emailUs: String
},
data () {
return {
copyright: "Copyright 2020: ",
startCR: this.footerData.titleFooter,
mNum: footerData.mainNumber,
oNum: footerData.otherNumber,
emailUs: footerData.emailUs
}
},
}
</script>
You are passing just 1 prop footerData but you have defined 4 in Footer.vue. Just define 1 prop in Footer.vue, and access as this.footerData[0].titleFooter, ...
export default {
props: {
footerData: Array,
},
data () {
return {
copyright: "Copyright 2020: ",
startCR: this.footerData[0].titleFooter,
mNum: this.footerData[0].mainNumber,
oNum: this.footerData[0].otherNumber,
emailUs: this.footerData[0].emailUs
}
},
}
You can handle the array in Footer.vue. You should define as many props as v-bind you are sending. https://v2.vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props
I am developing a Vue app with pimcore and twig in the backend. I have to create a component that receives the slot (another component), and render it inside, but with dynamic props.
Here is root in viani.twig.html:
<div>
<viani-accordion>
<viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox>
</viani-accordion>
</div>
There is nothing special. viani-accordion is a parent component and the viani-filter-checkbox is a slot, which I have to render with appropriate props.
Here you can see the VianiAccordion.vue:
<template>
<div class="wrapper">
<AccordionTitle v-for="(item, index) in dataToRender" :item="item" :key="index">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filter-rows="item.items"></slot>
</AccordionTitle>
</div>
</template>
<script>
import AccordionTitle from './Accordion-Title';
export default {
name: "Viani-Accordion",
components: {AccordionTitle},
data() {
return {
dataToRender: [
{
name: 'first item',
items: [
{
name: 'oil olive',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
},
{
name: 'second item',
items: [
{
name: 'subitem 1',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
}
]
}
},
}
</script>
Then I have another deeper child component Accordion-Title.vue that is responsible for rendering the slot (so I have to pass the slot through the multiple components):
<template>
<div v-if="isOpen" class="child-wrapper">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filterRows="item.items"></slot>
</div>
</template>
<script>
export default {
name: "Accordion-Title",
props: {
item: {
type: Object,
default: null
}
}
}
</script>
and finally Viani-FiltersCheckbox.vue:
<template>
<div>
//child component which we don't need in this case
<FilterCheckboxRow v-for="(item, index) in filterRows" :item="item" :key="index"/>
</div>
</template>
<script>
import FilterCheckboxRow from './FilterCheckboxRow'
export default {
name: "VianiFilterCheckbox",
components: {
FilterCheckboxRow
},
props: {
//here I expect to get array to render, but got default value (empty array)
filterRows: {
type: Array,
default: function () {
return []
}
},
},
}
</script>
So I need to pass the props (filterRows) to the component (Viani-FiltersCheckbox.vue), which is rendered as a slot. I have read this and this, but still don't get where the mistake and how to get the props I need.
It looks like you're trying to access your props through props.XXX. That's typically only done in templates for functional components. Otherwise, the props would be accessed without the props. prefix (i.e., props.item.items should be item.items).
And to pass filterRows from the scope data to the child component, declare a <template>, and then move your child into that, binding filterRows there:
<viani-accordion>
<!-- BEFORE: -->
<!-- <viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox> -->
<template v-slot="{filterRows}">
<viani-filter-checkbox :filterRows="filterRows"></viani-filter-checkbox>
</template>
</viani-accordion>
In a vuejs application there are 3 components - TopClicks.vue, TopImpressions.vue, TopCtr.vue. Each one of these is using vue-good-table (the component itself is not important, it can be any) to render almost the same table, but sorted differently:
../components/TopClicked.vue (almost 200 lines)
<template>
<div class="top-clicked">
<vue-good-table
mode="remote"
#on-page-change="onPageChange"
#on-sort-change="onSortChange"
:sort-options="{
enabled: true,
initialSortBy: {field: 'clicks', type: 'desc'}
}"
...
>
<template slot="table-row" slot-scope="props">
<template v-if="props.column.field === 'keyword'">
...
</template>
<template v-else-if="props.column.field === 'clicks'">
...
</template>
<template v-else-if="props.column.field === 'impressions'">
...
</template>
...
</template>
<template slot="loadingContent">
<span class="vgt-loading__content">
...
</span>
</template>
<template slot="emptystate">
...
</template>
</vue-good-table>
</div>
</template>
<script>
import { VueGoodTable } from 'vue-good-table';
export default {
name: 'TopClicked',
components: { VueGoodTable},
data() {
return {
columns: [
{
label: this.$t('keyword'),
field: 'keyword',
},
{
label: this.$t('clicks'),
field: 'clicks',
},
... more columns
],
};
},
};
</script>
The other two components - TopImpressions.vue and TopCtr.vue are almost the same, but with different :sort-options param.
My question is: How to organise my code to avoid making the same change multiple times in the passed to vue-good-table' component props or slot templates? How should a component look like, which passes default props and slots to another component, but these can be overwritten when needed?
It would be great if instead of copying the 200 lines of code from above, a child component (with base pros and slot templates) can be created and used like this
<vue-good-table-child
// this should overwrite default :sort-options in vue-good-table-child
:sort-options="{
enabled: true,
initialSortBy: {field: 'impressions', type: 'desc'}
}"
>
// this should overwrite default named slot "loadingContent" in vue-good-table-child
<template slot="loadingContent">
...
</template>
</vue-good-table-child>
This way all common code will be in a base component, and only the different props (or slot templates) should be passed to the child component.
I'd try merging the 3 nearly identical components into 1 component that receives the custom sortOptions it needs as a prop:
// This is the merged component, TopThings.vue, it replaces the instances
// of TopClicks.vue, TopImpressions.vue, and TopCtr.vue in the parent component
<template>
<div class="top-clicked">
<vue-good-table
...
:sort-options="sortOptions"
...
/>
...
</template>
<script>
...
props: {
sortOptions: {
type: Object,
required: true,
},
},
...
</script>
In your parent component, import the TopThings component and call it in place of each of your 3 previous components, where each implementation is passed its respective sortOptions:
// This is the parent component where the 3 tables are implemented as
// separate instances of <TopThings />
<template>
...
<TopThings // this is the TopClicks implementation
:sort-options="sortOptions.topClicks"
/>
...
<TopThings // this is the TopImpressions implementation
:sort-options="sortOptions.topImpressions"
/>
...
<TopThings // this is the TopCTR implementation
:sort-options="sortOptions.topCTR"
/>
...
</template>
<script>
components: {
TopThings.vue,
},
data() {
return {
sortOptions: {
topClicks: {
enabled: true,
initialSortBy: {field: 'clicks', type: 'desc'}
},
topImpressions: {
...
},
topCTR: {
...
},
},
};
},
I am creating a Vue JS app which will display a list of products that when clicked on will link through to a dynamic product by its ID (passed via Vue Router params). This bit works fine but what I need to do is once on that dynamic route, display all the data for that product by its ID. I'm still pretty new to Vue and confused by what approach to take. I don't think I need axios as this won't be an online app so no access to APIs. I also don't know if I need to go as far as using vuex. At present I'm trying to use a simple method to grab the data from my JSON file by the ID passed through the routes parameters. Here's what I have so far...
router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Products from './views/Products.vue'
import Product from './views/ProductSingle.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/products',
name: 'products',
component: Products
},
{
path: '/product/:id',
name: 'product',
component: Product,
props: true
}
]
})
Slider.vue - this component is called in Views/Products.vue and is what links through to each single product
<template>
<carousel
:items="3"
:loop="true"
:center="true"
:margin="0"
:dots="false"
:nav="true"
>
<div v-for="product in products" :key="product.productId">
<transition name="slide">
<router-link
:to="{
name: 'product',
params: { id: product.productId }
}"
>
<img src="http://lorempixel.com/100/100/city" :alt="product.name" />
<!-- <img
:src="require('../assets/images/sample-product/' + data.image)"
/>-->
</router-link>
</transition>
</div>
</carousel>
</template>
<script>
import json from '#/json/data.json'
import carousel from 'vue-owl-carousel'
export default {
data() {
return {
products: json
}
},
components: {
carousel
}
}
</script>
ProductSingle.vue
<template>
<div>
<p v-for="product in getData($route.params.id)" :key="product.productId">
{{ product }}
</p>
</div>
</template>
<script>
import json from '#/json/data.json'
export default {
data() {
return {
products: json
}
},
methods: {
getData(id) {
let data = this.products
data.filter(item => {
return item.productId == id
})
}
}
}
</script>
What I as expecting here is that my getData method would return the data for the product at the ID denoted through $route.params.id yet nothing is returned. A console.log(getData($route.params.id)) shows the following:
[{…}]
Which when expanded out shows 0: {__ob__: Observer} and if you expand that observer out there is indeed that data for
image: (...)
name: (...)
productId: (...)
My data.json file looks like the below:
[
{
"productId": 1,
"name": "Test 1",
"image": "sample.jpg"
},
{
"productId": 2,
"name": "Test 2",
"image": "sample.jpg"
},
{
"productId": 3,
"name": "Test 3",
"image": "sample.jpg"
},
{
"productId": 4,
"name": "Test 4",
"image": "sample.jpg"
}
]
Could really do with some guidance on how to approach this problem.
Many thanks in advance
You're missing a return here:
data.filter(item => {
return item.productId == id
})
Try this:
return data.filter(item => {
return item.productId == id
})
If you're not using Api or Vuex then you need to have all the data on a place where they can be fetched from multiple components.
data.json is fine for this.
On your child component, do the following. There is no need for data iteration.
<template>
<div>
{{ singleProduct }}
{{ singleProduct.productId }}
</div>
</template>
<script>
import json from '#/json/data.json'
export default {
name: 'ProductSingle',
data() {
return {
products: json,
singleProduct: undefined
}
},
computed: {
productId () {
return this.$route.params.id // From route
}
},
created () {
this.assignSingleProduct();
},
methods: {
assignSingleProduct() {
let data = this.products
data.filter(item => {
return item.productId == this.productId
})
// Assign to the data
this.singleProduct = data
}
}
}
</script>