Vue.js with Vuex, and custom components, #click method outputs undefined - javascript

I've a menu with about, news and contact buttons in addition to three buttons that allow to change the language of the label on the menu's buttons and the content of the text elements (variable contentText below).
I'm using Vue.js with custom components, as well as Vuex.js to store language states. I'm being able to select the language of the labels on the menu buttons. For instance, if I click on the button labeled fr, the labels on the meny bar change from about to à propos, news to nouvelles, etc., but for some reason that I cannot identify, when I click any one of the menu buttons, the click event doesn't trigger the visibility of the respective text elements. The code that deals with states is something along the following lines (Jsfiddle here):
Vue.use(Vuex)
var storelang = new Vuex.Store({
state: {
lang: {}
},
actions: {
lang: 'code'
},
mutations: {
code(state, ln) {
var jsontext = '{"aboutMenu":"About", "aboutText":"This is just a small text in English.", "newsMenu":"News", "newsText":"News written in the English language.", "contactMenu":"Contact", "contactText":"Contact info written in English."}'
if (ln === 'pt') {
jsontext = '{"aboutMenu":"Sobre", "aboutText":"Isto é um pequeno texto em Português.", "newsMenu":"Novidades", "newsText":"Novidades escritas em Português.", "contactMenu":"Contactar", "contactText":"Informação de contacto escrita em Português."}'
}
if (ln === 'fr') {
jsontext = '{"aboutMenu":"À propos", "aboutText":"Ceci est juste um petit texte en Français.", "newsMenu":"Nouvelles", "newsText":"Des nouvelles écrites en Français.", "contactMenu":"Contacter", "contactText":"Des informations dans la langue Française."}'
}
state.lang = JSON.parse(jsontext)
}
},
strict: true
})
The components with their respective templates, created with Vue.extend:
var contentBtn = Vue.extend({
template: '<button type="button" class="btn btn-default" #click="toggleAbout">{{lang.aboutMenu}}</button><button type="button" class="btn btn-default" #click="toggleNews">{{lang.newsMenu}}</button><button type="button" class="btn btn-default" #click="toggleContact">{{lang.contactMenu}}</button>'
})
var contentTxt = Vue.extend({
template: '<div v-show="aboutIsVisible">{{lang.aboutText}}</div><div v-show="newsIsVisible">{{lang.newsText}}</div><div v-show="contactIsVisible">{{lang.contactText}}</div>'
})
var langBtn = Vue.extend({
template: '<button type="button" class="btn btn-info" #click.prevent=activate("en")>en</button><button type="button" class="btn btn-info" #click.prevent=activate("pt")>pt</button><button type="button" class="btn btn-info" #click.prevent=activate("fr")>fr</button>',
methods: {
activate: function(x) {
storelang.actions.lang(x)
}
},
ready: function() {
return storelang.actions.lang('en') //default language
}
})
And my Vue instance, where I store the values concerning the visiblity of the text elements, register the components and declare the methods for the click events:
new Vue({
el: '#app',
data: {
aboutIsVisible: true,
newsIsVisible: true,
contactIsVisible: true
},
components: {
'langbtn': langBtn,
'contentbtn': contentBtn,
'contenttxt': contentTxt
},
methods: {
toggleAbout: function () {
this.aboutIsVisible = !this.aboutIsVisible
},
toggleNews: function () {
this.newsIsVisible = !this.newsIsVisible
},
toggleContact: function () {
this.contactIsVisible = !this.contactIsVisible
}
}
})
What am I missing?

You're trying to call toggleNews on a child component that doesn't have a method called toggleNews. That method is on the parent component. You'll need to move the button into the parent element, or utilize events to broadcast clicks from the child elements up to the parent.
I moved the child templates up into the parent and your code is working as expected: https://jsfiddle.net/674z6w0h/13/

Related

How to parse JSON data into HTML elemnets

I have a JSON data with HTML.
Like this:
"elements":[
{
"element":".dyno-text",
"value":"This fun here.<br> <button type='button' onclick='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success'
}
]
How will I parse this JSON data to Bootstrap Layout Design for example: Button will come to real.
Thanks
Uses Vue.component to assembly JSON as one component may be one solution.
But you may need to adjust the HTML template in JSON. Because for supporting some features such as onclick, binding class, it will be one serious headache.
Below is one demo which may provide you some ideas how to reach your goal.
new Vue ({
el:'#app',
data () {
return {
"elements":[
{
"element":"dyno-text",
"value":"This fun here.<br> <button type='button' #click='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success',
"methods": {
// changed onclick to #click, if you still like to use 'onclick' in the template, you have to define window.changeTheme
changeTheme: function(obj) {console.log('clicked')}
}
}
]
}
},
methods: {
createComponent(element) {
/*window.changeTheme = function () {
console.log('clicked by onclick')
}*/
return Vue.component(element.element, {
template: `<div ref="test">${element.value}</div>`,
mounted: function () {
this.$nextTick(() => {
this.$refs.test.querySelector('button.btn').classList.add(element.class)
// or adjust your template in JSON like `<button :class="classes"/>`, then binds element.class to data property=classes
})
},
methods: element.methods
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div class="container">
<div v-for="(item, index) in elements" :key="index">
<component :is="createComponent(item)"/>
</div>
</div>
</div>
You can do something like this; I changed the element in order to create a known htmlElement, so what you do here is to iterate your array of elements and you insert them inside the body, set the value, and toggle the class.
--Edit--
Cleanner solution thanks to pointing it out supercool
Documentation of classList
let elements=[
{
"element":"div",
"value":"This fun here.<br> <button type='button' onclick='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success'
}
]
elements.forEach((elemen,i)=>{
let createdElement= document.createElement(elemen.element)
createdElement.innerHTML = elemen.value
createdElement.classList.toggle(elemen.class)
document.body.appendChild(createdElement)
})

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);
});
},

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

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>

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>

Dynamic style binding in vue js to toggle a class

I am trying to bind an attribute to and id I have on a button to change its styling, basically I have a courses database table(using Laravel as backend) where each course has a Boolean named completed, All I want to do is if the course is completed(true) to render a specific id, and if it is not(false) to render another one, that's it, here's my code,
This is the blade template, this is inside a table:
<td>
<form method="POST" action="{{ route('course.completed', $course->name) }}" id="form-submit">
{{ csrf_field() }}
#if ($course->completed)
<button #click.prevent="onSubmit({{ $course }})" type="button" class="btn btn-sm" :id="cssClass">#{{ text }}</button>
#endif
</form>
And here is the vue instance, All i want to do here is to add an if condition that will set the cssClass properly to the name of the id that I want:
<script>
new Vue({
el: '#app',
data: {
cssClass: '',
text: ''
},
methods: {
onSubmit: function(course) {
axios.post('/MyCourses/' + course.name)
// .then(function (response){
// });
}
},
//Basically here's what I would like to be able to do
if (course.completed == true) {
this.cssClass = 'coursetogglingtrue',
this.text = 'Done!'
} else {
this.cssClass = 'coursetogglingfalse',
this.text = 'Not Yet!'
}
});
</script>
Right now the above code in the view instance errors out with "Uncaught SyntaxError: Unexpected token ." directed at the if statement course.completed, and it doesn't go away unless I delete the whole if statement, I know I'm not fetching the course from anywhere, I just don't know how yet, if there is a better idea/approach please let me know, and thanks for your time.
UPDATE:
Here's a change, this is what I have done so far,
As for the view:
#if ($course->pivot->completed == true)
<button #click.prevent="onSubmit({{ $course }})" type="button" class="btn btn-sm" :id="[isActive ? greenClass.aClass : redClass.aClass]">#{{ greenClass.text }}</button>
{{-- #else
<button #click.prevent="onSubmit({{ $course }})" type="button" class="btn btn-sm" :id="[isActive ? greenClass.aClass : redClass.aClass]"></button> --}}
#endif
Now as for the vue instance:
<script>
new Vue({
el: '#app',
data: {
isActive: true,
greenClass: {aClass: 'coursetogglingtrue', text: 'Done!'},
redClass: {aClass: 'coursetogglingfalse', text: 'Not Yet!'}
},
methods: {
onSubmit: function(course) {
axios.post('/MyCourses/' + course.name)
// .then(function (response){
// });
this.isActive = !this.isActive;
}
}
});
</script>
Since that I know the blade #if condition is passing as true, I can hardcode the is active is true, and when I press on the button I get what I wanted the class actually toggles, if that is not true I default to the other class, now the problem is I need that action to be performed on exactly one button, the one I pressed on, right now what happens is every single button toggles its class, I know that once again it's due to my code not being explicit about this, the thing is I don't know how yet, so please help if you have any idea, have been stuck on this for weeks and weeks, it's really frustrating that I can't get this one simple thing to work.
It doesn’t make sense to put an expression as a property key.
Try this:
new Vue({
el: '#app',
data: {
cssClass: '',
text: ''
},
methods: {
onSubmit: function(course) {
axios.post('/MyCourses/' + course.name)
// .then(function (response){
// });
if (course.completed == true) {
this.cssClass = 'coursetogglingtrue',
this.text = 'Done!'
} else {
this.cssClass = 'coursetogglingfalse',
this.text = 'Not Yet!'
}
}
}
});

Categories

Resources