Vuejs how to pass data as a prop to a child component - javascript

I build following component:
var Modal = Vue.component('modal', {
template: `
<div id="modal" class="modal">
<div class="modal-content">
<p>{{ link }}</p>
</div>
</div>
`,
props: [
'link'
],
});
And I would like to change the link data dynamically after I sent successfully an axios post.
My vue instance
new Vue({
el: '#form',
components: {
'modal': Modal
},
data: {
userId: '',
title: '',
body: '',
snippetLink: '',
},
methods: {
publish (e) {
var self = this;
axios.post('/snippets', {
title: this.title,
body: this.content,
})
.then(function (response) {
console.log("success");
self.link = response.data.hash; // Here I tried to add the reponse content to the vue component's p
})
.catch(function (error) {
console.log(error);
})
},
My Html Markup:
<modal link=""></modal>
...
<button type="button"
v-bind:class="{ 'modal-trigger': !isActiveModal }"
#click="publish">Publish
<i class="material-icons right">send</i>
</button>
So I am sending an axios post to my server successfully and get the data, I would like to open a modal window and put the data in a p tag, so far the modal pops up after my post but I am not sure my it does not change the content of the p tag.

As per my understanding , Snippetlink property can be used to hold data from server.
self.Snippetlink = response.data.hash;
and Pass Snippetlink to link attribute of the snippet-model
<snippet-modal :link="Snippetlink"></snippet-modal>

rupesh_padhye's answer is correct. This is just a further explanation.
First of all, to store the response data to a Vue component, you need to define a key in data for that purpose first. So to make this line work: self.link = response.data.hash;, you need to add link as a key for the Vue component's data:
data: {
userId: '',
title: '',
body: '',
snippetLink: '',
link: ''
},
If you mean snippetLink, change the line to self.snippetLink = response.data.hash;
Secondly, to pass the data as a prop to a child component, you have to specify the prop name, as well as the data key being passed. For instance, to pass your component's link as a prop with the same name to the snippet-modal component, you need: <snippet-modal :link="link"></snippet-modal>

Related

Angular: Show html page passing value

I have a page that I use to show 3 differents html depending on where it is called.
In a page I have:
openCreateAnagraphics(){
this.router.navigate(["/anagraphics", {customerUpdate: true}]);
}
So I'm navigate to anagraphics.
there I have in my anagraphics.page.ts:
ngOnInit() {
this.handleParams();
}
handleParams() {
let params = this.route.snapshot.params;
console.log("this.route.snapshot", this.route.snapshot)
console.log("params", params)
this.customerUpdate = params.customerUpdate ? params.customerUpdate : false
console.log("this.customerUpdate ", this.customerUpdate)
}
and in the anagraphics.page.html
<div *ngIf="....">
<div>
//this is showed if I come from another way
</div>
</div>
<div *ngIf="customerUpdate">
<div>
HELLO // there is the problem
</div>
</div>
but I see only a white page and my url is
http://localhost:4200/anagraphics;customerUpdate=true
I would to obtain that I if i click on the button that calls openCreateAnagraphics, I'll go in a page that show the html when customerUpdate = true.
EDIT 1:
In my app-routing.module.ts
{ path: 'anagraphics', loadChildren: () => import('./anagraphics/anagraphics.module').then(m => m.AnagraphicPageModule), canActivate: [AuthGuard], data: { configurator: true, customerUpdate: false } },
but in this way customerUpdate is always on false.
My solution reference link: https://angular.io/api/router/RouterEvent
In your .routing.module.ts file add data like the below.
const routes: Routes = [{
path: '',
component: AlertsComponent,
data: {
// What data you want to pass add that here
}
}]
Then get that value in your common component like below. (Like: app or nav component)
constructor(public router: Router) {
router.events.pipe(
filter((e: Event): e is RouterEvent => e instanceof RouterEvent)
).subscribe((e: RouterEvent) => {
// Get your route data here
});
}

Vue.js v-for in component renders when I visit the URL through a router-link but if I type the URL manually or refresh the page, v-for doesn't render

I have a pretty simple view that displays the icons of all characters from a certain game. If I were to visit the URL that displays that view through a router-link, everything works fine and I see the icons, however, if I then refresh the page, the icons disappear.
They also do not render at all if I manually type www.example.com/champions. Why is this happening.
My component:
<template>
<div class='wrapper'>
<div class="champions-container">
<div v-for='champion in champions' class="champion">
<img class='responsive-image' :src="'http://ddragon.leagueoflegends.com/cdn/' + $store.getters.version + '/img/champion/' + champion.image.full" alt="">
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
champions: this.$store.state.fullChampions
}
}
}
</script>
And my Vuex store where the champions are stored:
export default new Vuex.Store({
state: {
version: null,
fullChampions: null
},
mutations: {
version(state, data){
state.version = data.version
},
fullChampions(state, data){
state.fullChampions = data.fullChampions
}
},
actions: {
getVersion({commit}){
return axios.get("http://ddragon.leagueoflegends.com/api/versions.json")
.then((response) => {
commit('version', {
version: response.data[0]
})
})
.catch(function (error) {
console.log(error);
})
},
getFullChampions({commit, state}){
return axios.get("https://ddragon.leagueoflegends.com/cdn/" + state.version + "/data/en_US/championFull.json")
.then((response) => {
commit('fullChampions', {
fullChampions: Object.values(response.data.data)
})
})
.catch(function (error) {
console.log(error);
})
},
These might be because of these issues you encountered.
First: that component is not the one that dispatched your getFullChampions function in your vuex, might be in other component.
Second is that, you are already assigning the value of champions wherein the state fullChampions is not updated.
this.champions: this.$store.state.fullChampions // state.fullChampions might not yet updated.
Try this one might help you
watch: {
'$store.state.fullChampions': function() {
this.champions = this.$store.state.fullChampions
},
}
Last is to to do first a condition above your v-for to prevent the element
<div class="champions-container" v-if=""$store.getters.version>
<div v-for='champion in champions' class="champion">
<img class='responsive-image' :src="'http://ddragon.leagueoflegends.com/cdn/' + $store.getters.version + '/img/champion/' + champion.image.full" alt="">
</div>
</div>
Can you try to add this:
watch: {
$route: function(val, OldVal){
this.champions = this.$store.state.fullChampions;
}
},
after yuor data?
Upd.
If you are calling getFullChampions() action, then you can call it within watcher of my example instead of assigning to this.champions.

How To Display My Invoice Data In Invoice Template

I'm using Laravel 5.7 & VueJs 2.5.* ...
I have invoices table, i need to display specific invoice in a new component, so user can see whatever invoice he wants or print that invoice.
I don't know how to do that, i'm just playing around, if you could help me out, i'll be very grateful to you.
<router-link> to the component
<router-link to="/ct-invoice-view" #click="openInvoice(ctInvoice)">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Displaying Customer information here like this:
<div class="col-sm-4 invoice-col">
<address v-for="ctInvoice in ctInvoices" :key="ctInvoice.id">
<strong>Customer Info</strong><br>
Name: <span>{{ ctInvoice.customer.customer_name }}</span>
Invoice view component data() & method{}
data() {
return {
ctInvoices: {},
customers: null
};
},
methods: {
openInvoice(ctInvoice) {
axios
.get("api/ct-invoice/show/" + this.viewInvoice)
.then(({
data
}) => (this.ctInvoices = data.data));
},
Image for Better Understanding
You need to look at Dynamic Route matching: https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
Then you need to use axios.get in invoice views beforeMount function where this.$route.params.id will hold the invoice ID you want to load if the link is applied like so:
<router-link :to="`/ct-invoice-view/${ctInvoice.id}`">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Alternatively...
I suggest not navigating away from the list, it can be irritating for users having filtered the list then returning to it to look at more invoices and having to filter again unless the filter options and current results are sticky
There are a number of ways of doing this and they are lengthy to example, Typically I would make proper use of a modal and the invoice view load the data on display but to get you started a basic in page solution to experiment with, then try adapting in a reusable modal component later:
<button #click="showInvoice = ctInvoice.id">
<i class="fas fa-eye fa-lg text-blue"></i>
</button>
data() {
return {
loading: false,
invoice: {},
customers: null
};
},
computed: {
showInvoice: {
get: function() {
return this.invoice.hasOwnProperty('id');
},
set: function(value) {
if(value === false) {
this.invoice = {};
return;
}
// could check a cache first and push the cached item into this.invoice else load it:
this.loading = true;
axios.get("api/ct-invoice/show/" + value).then(response => {
// you could push the invoice into a cache
this.invoice = response.data;
}).cache(error => {
// handle error
}).finally(() => {
this.loading = false;
});
}
}
}
In view-invoice component have a close button with bind #click="$emit('close')"
Check this article for how $emit works: https://v2.vuejs.org/v2/guide/components-custom-events.html
<div v-if="loading" class="loading-overlay"></div>
<view-invoice v-if="showInvoice" :invoice="invoice" #close="showInvoice = false" />
<table v-else>....</table>
Hide the table when displaying the invoice, experiment with using v-show instead of v-if upon loosing table content state.
Inside your invoice view, property called invoice will contain the invoice data.
Check this article for how to use props: https://v2.vuejs.org/v2/guide/components-props.html
Hint: The #close listens to the $emit('close')
Could also make use of when switching between table and invoice view.
https://v2.vuejs.org/v2/guide/transitions.html
#MarcNewton
I did something like this, it's working for me, can u just review it for me:
<router-link> to the Invoice View component
<router-link v-bind:to="{name: 'ctInvoiceView', params: {id: ctInvoice.id}}">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Getting Data of Specific Invoice ID Like This:
created: function() {
axios
.get("/api/ct-invoice/" + this.$route.params.id)
.then(({
data
}) => {
console.log(data);
this.form = new Form(data);
})
.catch(error => {
console.log(error.response);
});
},

Vue.js + Require.js extending parent

I'm new to Vue.js but trying to get to grips with it. So far it has gone well and I've got quite far but I am stuck extending a parents template.
I am trying to make dashboard widgets that extend a default widget layout (in Boostrap). Please note that the below code is using Vue, Require, Underscore & Axios.
Parent file - _Global.vue
<template>
<div class="panel panel-default">
<div class="panel-heading">
<b>{{ widgetTitle }}</b>
<div class="pull-right">
<a href="#" v-on:click="toggleMinimized"
v-bind:title="(isMinimized ? 'Show widget' : 'Hide widget')">
<i class="fa fa-fw" v-bind:class="isMinimized ? 'fa-plus' : 'fa-minus'"></i>
</a>
</div>
</div>
<div class="panel-body" v-if="!isMinimized">
<div class="text-center text-muted" v-if="!isLoaded">
<i class="fa fa-spin fa-circle-o-notch"></i><br />
</div>
<parent v-if="isLoaded">
<!-- parent content should appear here when loaded -->
</parent>
</div>
</div>
</template>
<script>
export default {
// setup our widget props
props: {
'minimized': {
'default': false,
'required': false,
'type': Boolean
}
},
// define our data
data: function () {
return {
widgetTitle: 'Set widget title in data',
isLoaded: false,
isMinimized: this.$props.minimized
}
},
// when vue is mounted, open our widget
mounted: function () {
if(!this.isMinimized) {
this.opened();
}
},
// define our methods
methods: {
// store our widget state to database
storeWidgetState: function () {
// set our data to send
let data = {
'action' : 'toggleWidget',
'widget' : this.$options._componentTag,
'state' : !this.isMinimized
};
// post our data to our endpoint
axios.post(axios.endpoint, data);
},
// toggle our minimized data
toggleMinimized: function (e) {
// prevent default
e.preventDefault();
// toggle our minimized state
this.isMinimized = !this.isMinimized;
// trigger opened if we aren't minimized
if(!this.isMinimized) this.opened();
// save our widget state to database
this.storeWidgetState();
},
// triggered when opened from being minimized
opened: function () {
console.log('opened() method is where all widget logic should be placed');
}
}
}
</script>
Child file - Example.vue
Should extend _Global.vue using mixins and then display content within .panel-body
<template>
<div>
I want this content to appear inside the .panel-body div
{{ content }}
<img v-bind:src="image.src" v-bind:alt="image.alt"
v-if="image.src" class="img-responsive" style="margin: 0 auto" />
</div>
</template>
<script>
// import our widgets globals
import Global from './_Global.vue'
export default {
components: {
'parent': {
// what can I possibly put here??
}
},
// use our global mixin for all widgets
mixins: [Global],
// setup our methods for this widget
methods: {
opened: _.debounce(function () {
// make sure this can only be opened once
if(this.hasBeenOpened) return;
this.hasBeenOpened = true;
// temporarily allow axios to make external requests
let axiosHeaders = axios.defaults.headers.common;
let vm = this;
axios.defaults.headers.common = {};
axios.get('https://yesno.wtf/api')
.then(function (res) {
// set our content
vm.content = null;
// set our image content
vm.image.src = res.data.image;
vm.image.alt = res.data.answer;
})
.catch(function (err) {
// set our error text
vm.content = String(err);
})
.then(function () {
// this will always hit..
vm.isLoaded = true;
});
// restore our axios headers for security
axios.defaults.headers.common = axiosHeaders;
}, 300)
},
// additional data
data: function () {
return {
// set our widgets title
widgetTitle: 'Test title',
// logic for the specific widget
hasBeenOpened: false,
content: 'Loaded and ready to go...',
image: {
src: false,
alt: null
}
};
},
}
</script>
Currently my parent template is just completely overwriting my child view. The only way I can get it to work is by explicitly defining the template parameter inside components -> parent: {} but I don't want to have to do that...?
Ok, thanks to Gerardo Rosciano for pointing me the in right direction. I've used to slots to come up with an eventual solution. We then access parent methods and data attributes just to get everything working as it should.
Example.vue - our example widget
<template>
<div>
<widget-wrapper>
<span slot="header">Example widget</span>
<div slot="content">
<img v-bind:src="image.src" v-bind:alt="image.alt"
v-if="image.src" class="img-responsive" style="margin: 0 auto" />
{{ content }}
</div>
</widget-wrapper>
</div>
</template>
<script>
// import our widgets globals
import WidgetWrapper from './_Widget.vue'
export default {
// setup our components
components: {
'widget-wrapper': WidgetWrapper
},
// set our elements props
props: {
'minimized': {
'type': Boolean,
'default': false,
'required': false
}
},
// setup our methods for this widget
methods: {
loadContent: _.debounce(function () {
// make sure this can only be opened once
if(this.hasBeenOpened) return;
this.hasBeenOpened = true;
// temporarily allow axios to make external requests
let axiosHeaders = axios.defaults.headers.common;
let vm = this;
axios.defaults.headers.common = {};
axios.get('https://yesno.wtf/api')
.then(function (res) {
// set our content
vm.content = null;
// set our image content
vm.image.src = res.data.image;
vm.image.alt = res.data.answer;
})
.catch(function (err) {
// set our error text
vm.content = String(err);
})
.then(function () {
// this will always hit..
vm.isLoaded = true;
});
// restore our axios headers for security
axios.defaults.headers.common = axiosHeaders;
}, 300)
},
// additional data
data: function () {
return {
// global param for parent
isLoaded: false,
// logic for the specific widget
hasBeenOpened: false,
content: 'Loaded and ready to go...',
image: {
src: false,
alt: null
}
};
},
}
</script>
_Widget.vue - our base widget that gets extended
<template>
<div class="panel panel-default">
<div class="panel-heading">
<b><slot name="header">Slot header title</slot></b>
<div class="pull-right">
<a href="#" v-on:click="toggleMinimized"
v-bind:title="(minimized ? 'Show widget' : 'Hide widget')">
<i class="fa fa-fw" v-bind:class="minimized ? 'fa-plus' : 'fa-minus'"></i>
</a>
</div>
</div>
<div class="panel-body" v-if="!minimized">
<div class="text-center text-muted" v-if="!isLoaded">
<i class="fa fa-spin fa-circle-o-notch"></i><br />
Loading...
</div>
<div v-else>
<slot name="content"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
// get loaded state from our parent
computed: {
isLoaded: function () {
return this.$parent.isLoaded;
}
},
// set our data element
data: function () {
return {
minimized: false
}
},
// when the widget is mounted, trigger open state
mounted: function () {
this.minimized = this.$parent.minimized;
if(!this.minimized) this.opened();
},
// methods to manipulate our widget
methods: {
// save our widget state to database
storeWidgetState: function () {
// set our data to send
let data = {
'action' : 'toggleWidget',
'widget' : this.$parent.$options._componentTag,
'state' : !this.minimized
};
// post this data to our endpoint
axios.post(axios.endpoint, data);
},
// toggle our minimized state
toggleMinimized: function (e) {
// prevent default
e.preventDefault();
// toggle our minimized state
this.minimized = !this.minimized;
// trigger opened if we aren't minimized
if(!this.minimized) this.opened();
// save our widget state to database
this.storeWidgetState();
},
// when widget is opened, load content
opened: function () {
// make sure we have a valid loadContent method
if(typeof this.$parent.loadContent === "function") {
this.$parent.loadContent();
} else {
console.log('You need to define a loadContent() method on the widget');
}
}
}
}
</script>

Keeping track of added element in an array?

I'm playing around with vue.js and the .vue components, and as newbie, I'm wondering how can I keep track of the element I add in the array.
The situation is the following :
The user add a new element from a form
When he submit, the data are automatically added to a ul>li element, and a POST request is made to the API
When the POST is done, I want to update the specific li with the new data from the server.
The thing is, I can not target the last li because the server can take time to process the request (he do a lot of work), so the user may have added 1, 5, 10 other entries in the meantime.
So how can I do ?
Here's my code so far :
<template>
<form method="post" v-on:submit.prevent="search">
<input type="text" placeholder="Person name" required v-model="name" v-el="nameInput" />
<input type="text" placeholder="Company" required v-model="company" v-el="domainInput" />
<input type="submit" value="Search" class="btn show-m" />
</form>
<ul>
<li transition="expand" v-for="contact in contacts">
<img v-bind:src="contact.avatar_url" width="40px" height="40px" class="cl-avatar" />
<div class="cl-user">
<strong class="cl-name">{{contact.name}} <span class="cl-company">{{contact.company}}</span></strong>
</div>
</li>
</ul>
</template>
<script>
export default {
data () {
return {
contacts: [],
name: null,
company: null
}
},
methods: {
search: function (event) {
this.$http.post('/search', {
name: this.name,
company: this.company
}).then(function (xhr) {
// HERE ! How can I target the exact entry ?
this.contacts.unshift(xhr.data)
})
this.name = ''
this.company = ''
this.contacts.unshift({'name': this.name, 'company': this.company})
},
}
}
</script>
Thank you for your help ! :)
If you know that the name and company fields are unique you could search through the array to find it... otherwise you can just wait to append it to the array until the return function:
search: function (event) {
this.$http.post('/search', {
name: this.name,
company: this.company
}).then(function (xhr) {
this.contacts.unshift(xhr.data)
})
this.name = ''
this.company = ''
},
I finally found a working solution : I use a component instead of <li /> for each entries, and manage the state of these inside the component :
<ul>
<contact-entry v-for="contact in contacts" v-bind:contact="contact"></contact-entry>
</ul>
That way, when I add a new entry in the array (described above), a new instance of the component contact-entry is made.
Inside that component, I did the following :
<script>
export default {
props: ['contact'],
created: function () {
if (this.contact.id === undefined) { // If it's a new contact
this.$http.post('search/name', { // I do the post here
name: this.contact.name,
domain: this.contact.company.name
}).then(function (xhr) {
this.contact = xhr.data // This will update the data once the server has replied, without hassle to find the correct line
})
}
}
}
</script>
That's it ! :) In the parent's component, I removed the xhr request and simplified the method to :
<script>
export default {
data () {
return {
contacts: [],
name: null,
company: null
}
},
methods: {
search: function (event) {
this.name = ''
this.company = ''
this.contacts.unshift({'name': this.name, 'company': this.company})
}
}
}
</script>

Categories

Resources