see the attachement
I have records populated and having a following button on each row which has v-on:click="unfollow(dynamic_value)"
I want to change the button to 'Follow' after the click and on successful Ajax response like
I have no idea how to do that in vue.
Any help is appreciated.
I would suggest that you create a component for your records (see https://v2.vuejs.org/v2/guide/components.html)
You could create a component for each record with a boolean named "followed" in the data of your component and on the v-on:click directive write a ternary condition where both methods would make an ajax call and on the success callback it would change the state of the boolean.
Vue.component('follow-btn', {
template: '#follow-btn-template',
data: function () {
return {
followed: false
}
},
methods: {
follow: function (dynamic_value) {
// Ajax call and on success => change this.followed to true
},
unfollow: function (dynamic_value) {
// Ajax call and on success => change this.followed to false
}
}
})
:
<script id='follow-btn-template' type="text/x-template">
<button v-on:click="followed ? unfollow(dynamic_value) : follow(dynamic_value)">
{{followed ? 'Following' : 'Follow'}}
</button>
</script>
or use a conditional directive
<script id='follow-btn-template' type="text/x-template">
<button v-if="followed" v-on:click="unfollow(dynamic_value)"> Following</button>
<button v-else v-on:click="follow(dynamic_value)"> Follow</button>
</script>
For the ajax call you either use jQuery, vue-resource or Axios.
Related
I have a main page with 3 buttons that lead to the other 3 pages. All 4 pages (components) are connected through App.vue.
I use :click="goPage('pageName') at the main page with 3 different pageNames. And method:
goPage: function (status) {
this.$emit(status)
}
I am trying to pass my "pageName" to App.vue
<main-page v-if="status === 'mainPage'"
v-on:goPage="goPage($event)"
and
goPage: function(status){
console.log(status)
this.status = status
}
I used $emit approach and it worked for a single page. But I have no idea how to work with multiple calls.
Do $emit or should use something other?
You need to pass method as prop to your main page component. on main page component, get chageStatus function from prop then for each button set an #click to change your status to desire page name. on top component you need to define a method to change status for you:
<template>
<div>
<main-page v-if="status === 'mainPage'" :changeStatus ="this.changeStatus" />
<other-page v-else-if="status === 'otherPage'"/>
<another-page v-else-if="status === 'anotherPage'"/>
<div>
</template>
<script>
export default {
data(){
return {
status: 'mainPage'
}
}
methods: {
changeStatus(dst) {
this.status = dst
}
}
}
</script>
I have a page which displays a list of mutual funds. With each mutual fund, I have a button to display their NAV history. This button calls a component which has an embedded API call to fetch the NAV history. I pass the fund code for which the data is to be fetched as a prop to the component. However, I am not able to trigger the API call automatically when the prop is called.
this is my code as of now:
Parent component (main page):
<template>
<!-- some code -->
<a href="#" #click="fetchNavHistory(fund)">
<v-icon small>history</v-icon>
</a>
<!-- some more code -->
<NAVHistory
:show="showNavHistory"
:amfi_code="amfi_code"
ref="history"
#hide="showNavHistory=false"
/>
</template>
export default {
name: "FundList",
components: {
NAVHistory
},
data() {
return {
showNavHistory: false,
amfi_code: 0
}
},
methods:{
fetchNavHistory(fund){
this.amfi_code = fund.amfi_code
this.showNavHistory = true
var child = this.$refs.history
child.fetchNavHistory()
}
}
}
Child component (where NAV history is displayed):
<template>
<!-- some code -->
</template>
<script>
export default {
props: {
show: Boolean,
amfi_code: Number
},
data(){
return{
apiURL: process.env.VUE_APP_BASEURL,
navHistory: [],
}
},
methods: {
async fetchNavHistory(){
try{
const response = await fetch(this.apiURL + '/navhistory', {
method: 'POST',
body: JSON.stringify({"amfi_code": this.amfi_code}),
headers: {'content-type': 'application/json; charset=UTF-8'},
})
const data = await response.json()
console.log(data)
this.navHistory = data
} catch(error){
console.log(error)
}
}
}
}
</script>
At first I tried calling the fetchNavHistory() method on updated() event. But that kept calling the API non-stop when the component was displayed on the screen.
Then I tried adding a watch for the show prop. But that didn't work at all.
Finally, as a workaround, I called the API from the parent component itself. While that is working, it is calling the component with the previous value of the amfi_code, rather than the updated value. So the first time it gets called, the amfi_code is passed as 0.
Is there a way to safely trigger the API call when the component is displayed, i.e., the show prop is set to true?
You can try watch with deep:true option that way the watch will be triggered when a component will be mounted. Or you can call API on mounted hook and check show prop in it.
deep:true means a watch will look at if changes occur not only for a watched prop but additionally at all nested props.
immediate:true means that a watch will fire after a component is mounted (when a watched prop has initial value).
I have read this post which goes in depth about renderless components:
https://adamwathan.me/renderless-components-in-vuejs/
A renderless component would pretty much look like this:
export default {
render() {
return this.$scopedSlots.default({})
},
}
Now I would like to use this renderless component but also add a click listener to whatever is being passed into the slot.
In my case it would be a button. My renderless component would simply wrap a button and add a click listener to it, which in turn performs an AJAX request.
How would I go about adding a click listener to the element that is being passed into the slot?
Assuming you want to bind the click handler within the renderless component, I think from this post that you need to clone the vnode passed in to renderless, in order to enhance it's properties.
See createElements Arguments, the second arg is the object to enhance
A data object corresponding to the attributes you would use in a template. Optional.
console.clear()
Vue.component('renderless', {
render(createElement) {
var vNode = this.$scopedSlots.default()[0]
var children = vNode.children || vNode.text
const clone = createElement(
vNode.tag,
{
...vNode.data,
on: { click: () => alert('clicked') }
},
children
)
return clone
},
});
new Vue({}).$mount('#app');
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<renderless>
<button type="button" slot-scope="{props}">Click me</button>
</renderless>
</div>
Here's one way to go about this.
Your renderless component wrapper would consist of a single action (i.e. the function to issue the AJAX request) prop.
Vue.component('renderless-action-wrapper', {
props: ['action'],
render() {
return this.$scopedSlots.default({
action: this.action,
});
},
});
Then another component which uses the aforementioned wrapper would enclose a customisable slot with a #click handler, which invokes the action that is passed in when triggered.
Vue.component('clickable', {
props: ['action'],
template: `
<renderless-action-wrapper :action="action">
<span slot-scope="{ url, action }">
<span #click="action()">
<slot name="action"></slot>
</span>
</span>
</renderless-action-wrapper>
`,
});
Finally, wire up the specialised version of the wrapper.
<clickable :action="doAjaxRequest">
<button type="button" slot="action">Button</button>
</clickable>
Here's a live example of the above suggestion you can play around with.
Vue.directive('login-to-click', {
bind (el) {
const clickHandler = (event) => {
event.preventDefault()
event.stopImmediatePropagation()
alert('click')
}
el.addEventListener('click', clickHandler, true)
}
})
usage
<button #click="handleClick" v-login-to-click>CLICK</button>
handleClick is always triggered. How I can prevent that from directive? Tried with/without addEventListener "capture" flag without any luck.
For now I ended up with following solution:
Vue.prototype.$checkAuth = function (handler, ...args) {
const isLoggedIn = store.getters['session/isLoggedIn']
if (isLoggedIn) {
return handler.apply(this, args)
} else {
router.push('/login')
}
}
And then in component
<button #click="$checkAuth(handleClick)">CLICK</button>
From my understanding those are two different event handlers, you are only preventing the default event of the one bound in the directive, this has no influence on #click however, because you are not overwriting the click listener but adding a second one.
If you want the default of your #click binding to be prevented you can use #click.prevent="handleClick".
I don't think there's any way to do it from the directive, since you explicitly add another listener by binding #click to the button.
in my app I have many buttons (follow/like/add to watchlist/block
etc) that require user to be logged in to click on them
As with many things in Vue 2, this is a bad use case for a directive, but a very good use case for a component.
Here is a button that is only clickable when the user is authorized.
console.clear()
const AuthenticatedButton = {
props:["onAuth", "onNonAuth", "disable", "auth"],
template: `
<button #click="onClick"
:disabled="disableWhenNotAuthorized">
<slot>{{this.auth}}</slot>
</button>`,
computed:{
disableWhenNotAuthorized(){
return this.disable && !this.auth
}
},
methods:{
onClick(){
if (this.auth && this.onAuth) this.onAuth()
if (!this.auth && this.onNonAuth) this.onNonAuth()
}
}
}
new Vue({
el:"#app",
data:{
loggedIn: false
},
methods:{
onClick(){
alert("User is authenticated")
},
notAuthorized(){
alert("You are not authorized.")
}
},
components:{
"auth-btn": AuthenticatedButton
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<h3>User is {{!loggedIn ? 'Not Authorized' : 'Authorized'}}</h3>
<auth-btn :auth="loggedIn"
:on-auth="onClick"
:on-non-auth="notAuthorized">
Executes non auth handler when not authorized
</auth-btn> <br>
<auth-btn :auth="loggedIn"
:on-auth="onClick"
:disable="true">
Disabled when not authorized
</auth-btn> <br><br>
<button #click="loggedIn = true">Authenicate User</button>
</div>
With this button you can set an authorized handler and a non-authorized handler. Additionally, you can just disable the button if the user is not authorized.
In this component the authorized state is passed in through a property, but if you were using some form of state management (like Vuex) you could just as easily use that instead.
https://v2.vuejs.org/v2/guide/events.html#Event-Modifiers
There are a couple modifiers. .prevent is what I was looking for
<a href="#" #click.prevent="sendMessage('just one click');">
You can use:
event.target.preventDefault();
I am not sure why would want to do that though, since you are adding a click listener for a reason.
Perhaps you could clarify your question a bit more.
I am creating simple stuff seeking for capturing button click event to some text or get some alert. ReactJS JSX code is pasted below:
var SearchBar = React.createClass({
getInitialState: function() {
return {message: "test"};
},
test1: function(e) {
alert('hi');
this.setState({message: "New Message"});
},
render: function() {
var self = this;
return (
<div>
<button onClick={self.test1}>Change Message</button>
{this.state.message}
</div>
);
},
});
I use above SearchBar component in MVC cshtml as:
#Html.React("SearchBar", new{ })
Button get rendered on html page, but unable to change this.state.message value on click event. Where am I doing mistake?
There are two things that need to care about this issue
Add all jsx file uwins Script tag or using bundle in cshtml or in Views\Shared_Layout.cshtml file. e.g.
#System.Web.Optimization.Scripts.Render("~/bundles/main")
Call #Html.ReactInitJavaScript() method after that.
Now Click event surely get work.
Maybe you desire so:
render: function() {
return <div>
<button onClick={this.test1}>Change Message</button>
{this.state.message}
</div>
}
Use this instead self