How to display button link with vue.js? - javascript

My view is like this :
<div class="panel panel-default panel-store-info">
...
<div id="app">
<AddFavoriteProduct></AddFavoriteProduct>
</div>
....
</div>
My component is like this :
<template>
<a href="javascript:" class="btn btn-block btn-success" #click="addFavoriteProduct({{ $product->id }})">
<span class="fa fa-heart"></span> Favorite
</a>
</template>
<script>
export default{
name: 'AddFavoriteProduct',
props:['idProduct'],
methods:{
addFavoriteProduct(event){
event.target.disabled = true
const payload= {id_product: this.idProduct}
this.$store.dispatch('addFavoriteProduct', payload)
setTimeout(function () {
location.reload(true)
}, 1500);
}
}
}
</script>
When click button favorite, it will call controller on the laravel
I had register on the app.js like this :
...
import AddFavoriteProduct from './components/AddFavoriteProduct.vue';
...
components: {
...
AddFavoriteProduct
},
...
When executed, the button favorite does not appear.
Please help.
UPDATE
There exist error like this :
[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option. (found in root instance)
Whereas I had register it

Three fixes that I can see...
First, omit name: 'AddFavoriteProduct' in your component (it's not required for single-file components) and use kebab-case for the component in your template. Second, you appear to be missing the id-product prop
<add-favorite-product :id-product="someProductIdFromSomewhere"></<add-favorite-product>
Third, you don't use interpolation in bound properties and you don't even need to pass anything other than the $event to your addFavoriteProduct method
#click="addFavoriteProduct($event)"

HTML is case-insensitive, so your custom button element <AddFavoriteProduct></AddFavoriteProduct> is being interpreted as your Vue warning reports: <addfavoriteproduct>
When you name your component with camelCase or PascalCase, the corresponding tag name you should be using in your markup should be kebab-cased like so:
<add-favorite-product></add-favorite-product>
Vue knows to do the conversion from "dash-letter" to "uppercase-letter".

I am not used to vuex, as I generally implement my own store as a POJO object - not graduated with the learning curve of vuex. So can't provide a working solution with vuex, however to my knowledge, you need to kind of import the action(s) in your component - you can try the below
<template>
<a href="javascript:" class="btn btn-block btn-success" #click="addFavoriteProduct({{ $product->id }}, $event)">
<span class="fa fa-heart"></span> Favorite
</a>
</template>
<script>
export default{
props:['idProduct'],
vuex: {
actions: {
setFavoriteProduct: setFavoriteProduct
// assuming you named the action as setFavoriteProduct in vuex
// avoid duplicate naming for easy debugging
}
}
methods:{
addFavoriteProduct(productId, event){
event.target.disabled = true
const payload= {id_product: this.idProduct}
// this.$store.dispatch('addFavoriteProduct', payload)
this.setFavoriteProduct(payload);
setTimeout(function () {
location.reload(true)
}, 1500);
}
},
}
Note: From Vuex2 the store.dispatch() accepts only one argument (not a problem with your current use case), however if you need to pass multiple arguments to your action you can work around like
store.dispatch('setSplitData',[data[0], data [1]])
// in the action:
setSplitData (context, [data1, data2]) { // uses ES6 argument destructuring
OR
//... with an object:
store.dispatch('setSplitData',{
data1: data[0],
data2: data [1],
})
// in the action:
setSplitData (context, { data1, data2 }) { // uses ES6 argument destructuring
//source: LinusBorg's comment at https://github.com/vuejs/vuex/issues/366

Are you getting any error?
One mistake I see in your code is, you are taking one parameter: event, while you are trying to pass {{ $product->id }} to it, which seems a laravel variable. (Sorry, I don't know laravel)
If you want both event and product->id in method, you have to pass both parameters from HTML, like it is in docs with help of $event
<template>
<a href="javascript:" class="btn btn-block btn-success" #click="addFavoriteProduct({{ $product->id }}, $event)">
<span class="fa fa-heart"></span> Favorite
</a>
</template>
<script>
export default{
name: 'AddFavoriteProduct', // I am not sure if name is needed, don't remember seeing it in docs
props:['idProduct'],
methods:{
addFavoriteProduct(productId, event){
event.target.disabled = true
const payload= {id_product: this.idProduct}
this.$store.dispatch('addFavoriteProduct', payload)
setTimeout(function () {
location.reload(true)
}, 1500);
}
}
}
</script>
Another problem is you are expecting a prop idProduct, which is not being passed to component, You have to pass it like this in kebab-case:
<div class="panel panel-default panel-store-info">
...
<div id="app">
<AddFavoriteProduct id-product="4"></AddFavoriteProduct>
</div>
....
</div>

Related

VueJS: Method is not defined on the instance

I made a component for a drag and drop element. What I want to do is use my .vue template file, instead of writing all the HTML on the single line after the template. (When I do write it in the component itself, it works fine, but just doesn't look good.) I need it to be a component because it can be that there is more than one component on one page.
Here is my .js file:
import Vue from 'vue';
import SingleFileUpload from './single-file-upload.vue'
Vue.component ('test', SingleFileUpload, {
delimiters: ['${', '}'], // Avoid Twig conflicts
data() {
return filelist // Store our uploaded files
},
methods: {
onChange() {
this.filelist = [...this.$refs.file.files];
},
remove(i) {
this.filelist = [];
},
dragover(event) {
event.preventDefault();
// Add some visual fluff to show the user can drop its files
if (!event.currentTarget.classList.contains('highlight')) {
event.currentTarget.classList.remove('bg-haze');
event.currentTarget.classList.add('highlight');
}
},
dragleave(event) {
// Clean up
event.currentTarget.classList.add('bg-haze');
event.currentTarget.classList.remove('highlight');
},
drop(event) {
event.preventDefault();
this.$refs.file.files = event.dataTransfer.files;
this.onChange(); // Trigger the onChange event manually
// Clean up
event.currentTarget.classList.add('bg-haze');
event.currentTarget.classList.remove('highlight');
}
},
template: SingleFileUpload
})
new Vue({
el: '#App',
})
This is my .vue file
<template>
<div class="file-upload__wrapper">
<div
class="file-upload__drop-area"
#dragover="dragover"
#dragleave="dragleave"
#drop="drop"
>
<input
type="file"
name="file-upload-bank"
id="file-upload-bank"
class="d-none"
#change="onChange"
ref="file"
accept=".pdf,.jpg,.jpeg,.png"
/>
<ul v-if="this.filelist.length" v-cloak>
<li v-for="file in filelist" v-bind:key="file.id">
<span>Bestand: ${ file.name }</span
><button
type="button"
#click="remove(filelist.indexOf(file))"
title="Verwijder bestand"
>
<i class="icon-cross"></i>
</button>
</li>
</ul>
<label for="file-upload-bank" class="block cursor-pointer" v-else>
<div>Kies een bestand of drag and drop het bestand</div>
</label>
</div>
</div>
</template>
In my page I use to place it.
Unfortunately, I get this error (which I get for all methods I use in the template).
vue.common.dev.js:630 [Vue warn]: Property or method "dragover" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Test> at assets/scripts/vue-components/forms/single-file-upload.vue
<Root>
Who knows what I do wrong and how to fix it?

Props are somehow getting mixed between components

I'm passing two different arrays to the same component but two different instances of it. Then inside those instances I do a v-for and send a single item of array to another component using props. The problem is that when I inspect the Vue tools for the last component, I see that the prop is good but when I try to assign it to data it returns the prop from previous array(the one that was sent to another component).
Laravel:
<co-notifications class-name="writable" nots="{{ $notifications[0] }}"></co-notifications>
<co-notifications class-name="writable extended" nots="{{ $notifications[1] }}"></co-notifications>
CoNotifications:
<template>
<div>
<div v-for="notification in notifications">
<co-notification-item :class-name="className" :not="notification"></co-notification-item>
</div>
</div>
</template>
<script>
import notificationComponent from './NotificationComponent.vue';
export default {
props: ['className', 'nots'],
components: {
'co-notification-item': notificationComponent
},
// data() {
// return {
// notifications: JSON.parse(this.nots),
// }
// },
computed: {
notifications(){
return JSON.parse(this.nots)
}
},
}
</script>
CoNotificationItem
<template>
<div :class="['tableItem',className]">
<div class="textareaWrapper">
<input type="text" class="form-control" placeholder="Title" v-model="notification.title" v-if="notification.type === 'main'">
<textarea class="form-control" rows="6" placeholder="Some text..."
v-model="notification.text"></textarea>
</div>
<div class="buttonWrapper">
<button type="button" class="btn btn-success" #click="updateNotification"><i
class="fe fe-check mr-2"></i>Save
</button>
<button type="button" class="btn btn-danger" #click="deleteNotification"><i
class="fe fe-check mr-2"></i>Delete
</button>
</div>
</div>
</template>
<script>
import notificationComponent from './NotificationComponent.vue';
export default {
props: ['className', 'not'],
components:{
'co-notification-item': notificationComponent
},
data(){
return {
notification: this.not
}
},
methods: {
updateNotification(){
this.notification.text = "test";
},
deleteNotification(){
}
}
}
</script>
As for the data in arrays, I have 2 in the arr(0) and 2 in arr(1).
When I open Vue tools on the FIRST notifications I see this (THIS IS GOOD)
However, when I open other notifications that read from arr(1) I see this (this is obviously not how it's supposed to be)
As you can see I used computed for the CoNotification but if I remove it and use only data() both nots recieve the same array, but if I use computed it is okay. However, I can't use computed in CoNotificationItem beacuse I need to have it in data() so I can bind it with v-model.
So, my question is, how to make notification on the CoNotificationItem be the same as not (variable) but be accessible in data() so I can put v-model to it - why am I getting mixed values?
Note: I also tried with computed and watch and created/mounted.
I've been stuck at this problem for half the day and I searched my as* off both in official docs and tutorials/questions on stackoverflow and whatnot.
Some searches that I tried :
Vue.js passing props to data
Passing data from Props to data in vue.js
https://forum.vuejs.org/t/update-data-when-prop-changes-data-derived-from-prop/1517
Adding unique key property to each v-for item will solve the problem
<div v-for="notification in notifications" :key="someUniqueKey">
<co-notification-item :class-name="className" :not="notification"></co-notification-item>
</div>
I can not explain it clearly. But as i understand, Vue tracking elements by key property. Give these elements unique key will tell Vue that they are different elements. So it will prevent Vue re-using prop and data.
If anyone can explain more detail and refer document please add comment or new answer. I will delete my answer.

Ember JS - Update property on all instances of a component

Is it possible to update a property on all instances of a component?
If I have 10 instances of the component below on a page, I would like to set the currentTrack property to false on all of them. Is this possible? Can it be done from inside one of the components?
import Ember from 'ember';
export default Ember.Component.extend({
currentTrack: true,
});
I'm using Ember 2.12
You can use Ember.Evented for this use case.
Here, there is a simple twiddle for it.
template.hbs
{{your-component-name currentTrack=currentTrack}}
{{your-component-name currentTrack=currentTrack}}
{{your-component-name currentTrack=currentTrack}}
// btn for disabling
<a href="#" class="btn" onclick={{action 'makeAllcurrentTracksFalse'}}>To false</a>
controller.js
currentTrack: true,
actions: {
makeAllcurrentTracksFalse() {this.set('currentTrack', false)}
}
or in your-component-name.js - you can use the same action as above and it will be applied to all components
How about you create entries for what ever thing your're trying to achieve.
const SongEntry = Ember.Object.extend({
});
To create an entry you would call (probably add a song to playlist?)
songs: [],
addNewSongToList: function(songName) {
const newEntry = MyMusicEntry.create({
isCurrent: false,
title: songName
});
this.get('songs').pushObject(newEntry);
},
activateNewSong: function(newSongToActivate) {
this.get('songs').forEach(s => s.set('isCurrent', false);
newSongToActivate.set('isCurrent', true)
}
Template would look like this
{{each songs as |song|}}
{{my-song-component songEntry=song changeSong=(action "activateNewSong")}}
{{/each}}
//my-song-component.js
<div class="song-layout" {{action "changeSong" song}}>

Binding Input in Vue not working

I would like to call a function with a value when a user starts typing in an input box. I have tried two approaches.
The first approach is trying to use two-way binding to a model. However, after following the documentation I get an error.
Here is the example from the official docs:
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>
var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
})
And here's my example:
<template lang="html">
<input
type="text"
v-model="handle"
/>
</template>
<script>
export default {
data: {
handle: 'model',
}
};
</script>
I am writing this as part of an application so I chose not to recreate the Vue instance and I declared that elsewhere. However, I get this error:
Property or method "handle" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
A second approach I've tried is this calling a function directly from the view via an event handler. I'm coming from React so this is my preferable approach. However, the function has undefined as an input value meaning it's not picking up the value of the input.
<template lang="html">
<input
type="text"
v-on:keyup="handleInput()"
/>
</template>
<script>
export default {
methods: {
handleInput(input) {
// input -> undefined
},
},
};
</script>
I really can't see why neither of these works. Wouldn't the expected behavior of an input listener would be to pass the value?
Where am I going wrong?
It seems like you might have to do something like this: How to fire an event when v-model changes ? (vue js). What I don't understand is why you have to manually attach a watcher when you have assigned a v-model? Isn't that what a v-model is supposed to do?
What finally worked was this:
<template lang="html">
<input
type="text"
v-model="searchTerm"
#keyup.enter="handleInput"
/>
</template>
<script>
export default {
data() {
return { searchTerm: '' }
},
methods: {
handleInput(event) {/* handle input */},
},
};
</script>
Shouldn't data be a function on your first example? I think this is how it works for vue components.
<script>
export default {
data: function () {
return { handle: 'model' }
}
};
</script>
I think this was explained somewhere on vuecasts.com, but I might be wrong. :)

vue.js list ( template ) binding not updating when changing data from directive

First of all : I'm using laravel spark and the given setup of vue that comes with spark.
I have a "home" component with the prop "custom". Within custom there's a "passwords" array. (Entry added by code of directive, it's initialized empty)
My component ( alist) which should be bound against the data
<template id="passwords-list-template">
<div class="password" v-for="password in list">
<ul>
<li>{{ password.name }}</li>
<li>{{ password.description }}</li>
</ul>
</div>
</template>
<script>
export default {
template: '#passwords-list-template',
props: ['list'],
};
</script>
Usage
<passwords-list :list="custom.passwords"></passwords-list>
Using vue devtools I can see that my data is updating, however my list is not. Also other bindings like
<div v-show="custom.passwords.length > 0">
Are not working ...
UPDATE : Parent component (Home)
Vue.component('home', {
props: ['user', 'custom'],
ready : function() {
}
});
Usage
<home :user="user" :custom="spark.custom" inline-template>
Update 2: I played around a little bit using jsfiddle. It seems like changing the bound data object using $root works fine for me when using a method of a component. However it does not work when trying to access it using a directive
https://jsfiddle.net/wa21yho2/1/
There were a lot of errors in your Vue code. First of all, your components where isolated, there wasn't an explicit parent-child relationship.Second, there were errors in the scope of components, you were trying to set data of the parent in the child, also, you were trying to set the value of a prop, and props are by default readonly, you should have written a setter function or change them to data. And finally, I can't understand why were you trying to use a directive if there were methods and events involve?
Anyway, I rewrote your jsfiddle, I hope that you find what you need there. The chain is Root > Home > PasswordList. And the data is in the root but modified in home, the last component only show it. the key here are twoWay properties, otherwise you wouldn't be able to modify data through properties.
Here is a snippet of code
Home
var Home = Vue.component('home', {
props: {
user: {
default: ''
},
custom: {
twoWay: true
}
},
components: {
passwordList: PasswordList
},
methods: {
reset: function () {
this.custom.passwords = [];
}
}
});
// template
<home :custom.sync="spark.custom" inline-template>
{{custom | json}}
<button #click="reset">
reset in home
</button>
<password-list :list="custom.passwords"></password-list>
<password-list :list="custom.passwords"></password-list>
</home>
Here is the full jsfiddle

Categories