Pass data between extended components OR call method of component - vue.js

I have main component which fetch list of users in Created() method. Two next components extended of main component. In this case created method calls each time at extended components created. So i have extra call of fetch list of users. Goal is fetch users one time and pass it to extended components.
For example:
https://jsfiddle.net/apokjqxx/45/
In example we can see that Created() method of main components called two time but need one time and pass data to extended components. What's best solution for this?

Each extended component is a different object instance, that's why Created() and fetch are been executed twice.
What you need is another piece of code acting like a storage (cache) for your item's list, this piece of code should be instantiated once and shared among your components.
Vuex is probably a good option for that "piece of code"

If your math and physics component share the same data, and even have a lot in common in template, why not make them one component? This will solve your duplicated fetch issue, and you can easily rearrange your template by wrapping your current template with a v-for and use
data() {
return {
lists: [
{course: 'math', people: ['mike', 'arnold', 'tony']},
{course: 'physics', people: ['mike', 'arnold', 'tony']}
]
};
}
as your data.

Related

Which Laravel Vue best way to manage filtering from filters in Laravel Vue?

I have a page in which, through a form, i can redirect mywebsite to a page, that will show my apartments_list.
I will recover the apartaments list from my database, using data from filters with WHERE keyword, and show it in my apartments_list page.
My apartments_list page will be handled with Vue. That page must have the same filters as the other one. If i modify the filters, the result must change, there mustn't be refresh.
Which is the best way to manage this?
I thought to pass to the apartments_list page two different list:
One is the list_filtered, the other one is the complete list.
If i change the filter in my apartments_list page, through Vue, ill modify a copy of my complete list respecting the filters filtering.
There is a better way to do this?
Im using Laravel and Vue.
You can use Vue.js computed properties to achieve this on your Vue.js component.
Let's say you have the full list of Listings on your Vue.js data and want to filter it based on choices.
export default {
data() {
return {
selectedCity: "Istanbul",
listings: [], // Apartment Listings, will be filled by the API
};
},
computed: {
// a computed list of filtered listings
filteredListings() {
return this.listings.filter((listing) => {
return listing.city.toLowerCase().includes(this.selectedCity.toLowerCase());
});
},
},
};
Of course you need to edit filtering logic but this is a way to solve your need on Vue.js with a complte list of listings.
A better version can bu set up by watching filter changes and ask a list of filtered result for api on each change. To understand this you can check Vue.js Watchers.

Initially hide first group in Vue-Formulate repeatable group

I'm using Vue-Formulate's Repeatable Groups. For my requirements:
A group is optional to add
If a button is pressed to add a group, then the fields in the group must be validated
The form should not initially show the group; it should show the button to add a group
For example, the desired initial appearance is in the following screenshot, which I generated after clicking the "remove" / X button in the linked codesandbox:
I've mocked this up at codesandbox here: Vue Formulate Group with Button to Start
Is this possible? If so, how?
May 2021 UPDATED WORKAROUND
In #braid/vue-formulate#2.5.2, the workaround below (in Research: A hack that seems to UPDATE: USED TO work) no longer works, using a slot to override the close button, save a ref, and trigger a click does. See also the related feature request at https://github.com/wearebraid/vue-formulate/issues/425.
<script>
export default {
// ... fluff omitted
async mounted() {
await this.$nextTick();
if (!this.hasMessages) {
// See also feature request at https://github.com/wearebraid/vue-formulate/issues/425
this.$refs.closeButton.click();
}
},
};
</script>
<template>
<FormulateInput
type="group"
name="rangeMessages"
:minimum="0"
repeatable>
<!-- See https://vueformulate.com/guide/inputs/types/group/#slots -->
<template #remove="{removeItem}">
<button ref="closeButton" #click.prevent="removeItem"/>
</template>
</FormulateInput>
</template>
Research - Vue-Formulate's Docs
In Vue-Formulate's docs on input with type="group"'s props and slots, there is a minimum prop. I've set that to zero, but that doesn't change the initial appearance. I do see multiple slots, but I'm not quite sure which one to use or if I could use any of them to achieve what I want; it seems like default, grouping, and repeatable might be useful in preventing the initial display of the first group.
Research - Vue-Formulate's Tests
I see that FormulateInputGroup.test.js tests that it('repeats the default slot when adding more', so the default slot is the content that gets repeated. According to the docs, the default slot also receives the index as a slot prop, so that could be useful.
Research - Vue Debugger
The item which I want to initially remove is at FormulateInputGroup > FormulateGrouping > FormulateRepeatableProvider > FormulateRepeatable > FormulateInput:
When I remove the initial item to match the desired initial layout, the group hierarchy changes to:
<FormulateInput><!-- the input type="group" -->
<FormulateInputGroup>
<FormulateGrouping/>
<FormulateAddMore>...</FormulateAddMore>
</FormulateInputGroup>
</FormulateInput>
Based on this change, I would expect that I need to modify FormulateGrouping to get the desired initial appearance, but I haven't found in the source what items are available to me there.
Research: A hack that seems to UPDATE: USED TO work
This hack worked in v2.4.5, but as of 2.5.2, it no longer works. See top of post for an updated workaround.
In the mounted hook, when I first render the form, I can introspect
into the formValues passed to v-model to see if the group lacks an
initial elements that is filled out. If so, then I can make use of a
ref msgs on the FormulateInput of type group to then call
this.$refs.msgs.$children[0].$children[0].removeItem(), which
triggers an initial remove of the (empty) item. This is super hacky,
so I'd prefer a better way, but it kind of works. The only problem is
that it validates the fields when clicking on the button, before any
input has been entered.
This is a fair question, and Vue Formulate used to support the behavior of just using an empty array to begin with, however it became clear that it was confusing to users that their fields would not show up without an empty object [{}] when they bound the model, so a change was made to consider an initial value of an empty array an "empty" field and pre-hydrate it with a value. Once that initial hydration is completed however, you can easily re-assign it back to an empty array and you're good to go. This is easily done in the mounted lifecycle hook:
...
async mounted () {
await this.$nextTick()
this.formData.groupData = []
}
...
Here's a fork of your code sandbox: https://codesandbox.io/s/vue-formulate-group-with-button-to-start-forked-32jly?file=/src/components/Reproduction.vue
Provided solutions weren't working for me but thanks to previous answer I've managed to find this one:
mounted(){
Vue.set(this.formData, "groupData", [])
},
which does same effect as
data(){
formData: {
groupData: [],
},
},
mounted(){
this.formData.groupData = []
},

VueJs Getting emit value from a past event

I am emitting an event using an eventBus. I am looking to get the value from this on another component. At the moment, this triggers when something is selected, however, I want to be able to get the data from a Past event. For example, I have emitted the value when selecting an item. In another component, I wish to use this value again.
Here is the code to send the event:
clientId(client) {
eventBus.$emit("selected", client);
},
In the component, I am receiving it like this:
created() {
eventBus.$on("selected", index => this.client(index));
},
However it is not working for a past event.
I would like this to update a data value in a different component so that i can use the value.
How can I get the value of a past event?
You need a store for preserving data, simply inside your event bus update the store and then you can use a getter to retrieve the data from the store in any component. If you are not familiar take a look at the vuex docs.

possible to dynamically add objects to an array and have Vuejs detect it

edit 2
I think the issue is how I'm referring to an array from a parent component. A fiddle is provided in the comments.
I have an app where we want to be able to add items to a menu_header. I have tried pushing to the bottom of the array but Vuejs doesn't seem to be detecting it.
I have read this section https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats and am trying to make this work but I'm not sure if this is possible.
Something like:
var obj = { name: "my name" }
menu_header.items.push(obj);
Do I need to use this.$set syntax? I really need to add into the middle of an array via splice.
edit 1
So this is a component that is recursive (ie a menu_header can have many menu_headers). I have tried adding a simple button in a menu_item to add to the parent component like this:
methods:{
addItem: function(){
var items = this.$parent.$data.menuHeader.menu_items;
var obj = { header: "my header", detail: "this detail"}
console.log("11 items length: " + items.length);
items.splice(1,0,obj);
console.log("22 items length: " + items.length);
},
The count of the number of items is incremented but the view doesn't rerender. This component is nested 3 levels deep (a Menu component has many MenuHeader components which can have many MenuItem components and also have many MenuHeader components). I'm pretty sure it's a reactivity / array issue - but not sure about exact problem.
Really the issue here was that you should use a key with a list in order for Vue to property render it in all cases and when you are iterating over a component you must use a key. The code in the fiddle is properly adding the elements to the array and Vue is detecting the changes, it just doesn't properly render the list because of it's update strategy. Using a key fixes that.
To that end I modified these lines in the template.
<div v-for="menu_header in menu.menu_headers" :key="menu_header.name">
and
<div v-for="(menu_item, idx) in menuHeader.menu_items" :key="menu_item.header">
The best key for these is some unique property of the object in the list. The above uses name and header, but I expect with real code you could come up with a better key.
It's best to get in the habit of always adding a key whenever you render a list in Vue.

VueJS Component Input Sync

I want to create components which have input which two-way bind to the local scope of the component.
Without a component, I would create a new Vue instance and then set my data to what I need. Then using v-model, bind an input to that data and it can be manipulated from the input.
However, converting the same code to a component, I cannot for the life of me get any input in a component to bind to its data. I have tried props, :data.sync, data attributes but no matter what I have tried, the input within a component does nothing.
I have created a JSFiddle to illustrate this:
https://fiddle.jshell.net/f0pdmLhy/2/
What I would like to happen is the input in the component to two way bind to the err variable, just like the non component version underneath.
How would I accomplish this?
I basically want to create components that I can instansiate with ajax data and then populate the inputs. The inputs could then update the data and I can use a save method to send the data to the server. Can this even be done using components?
So there are a couple of things:
The external resource you were using was somehow faulty. I've used
jsfiddle default Vue instance and it works fine.
When you declare a component, you should not define the data as an object, but as a function returning an object. Read here: https://vuejs.org/guide/components.html#Component-Option-Caveats
A working example here: https://fiddle.jshell.net/by4csn1b/1/
Yes, with components, the reactivity can be accomplished just like with an instance.
One catch with components, is that data must be a function that returns an object.
Also, to maintain the two way binding, use v-model in your input.
Vue.component('ii', {
template: '<span>{{err}}</span><input type="text" v-model="err"><hr>',
data: function () {
return {
err: 123
}
}
})
Fiddle: https://fiddle.jshell.net/f0pdmLhy/25/