Same function Buttons but used them separately Vue & Vuetify - javascript

Codepen Demo
new Vue({
el: '#app',
data: () => ({
Frequired: true,
Lrequired: false
}),
methods: {
handleChanges () {
this.Frequired = !this.Frequired;
this.Lrequired = !this.Lrequired;
}
}
})
How do I use the same function botton /checkout but use them separately?
In the example above, I would love to do a check for Required First Name Field and do a check on Last Name Field, now the function links to both checkbox, would love to know a clean way to click them separately.

you may remove handleChanges and use something like this
#click="Frequired = !Frequired"
and
#click="Lrequired= !Lrequired"
demo: https://codepen.io/jacobgoh101/pen/ZoabNB?editors=1000

** as #Jacob Goh said, or if you need more logic then
You can pass a param instead of doing
<v-checkbox label="Required" #click="handleChanges"></v-checkbox>
you can do
<v-checkbox label="Required" #click="handleChanges('f')"></v-checkbox>
and check for it in your method
methods: {
handleChanges (param) {
if(param==='f'){
this.Frequired = !this.Frequired;
// do more stuff
}else {
this.Lrequired = !this.Lrequired;
// do more stuff
}
}
}

Related

Can I Pass a Function To a Props on a VueJS Component

I have a rather simple component. Basically it is a dropdown select and I need to run some custom code that is in an external javascript function. However, this prop is not required on every instance of the component. So sometimes there may be nothing in the prop. Other times it might do one thing, others might do something else.
<template id="drop-list-template">
<select class="form-control"
v-model="value"
v-bind:class="{ required: isRequired, invalid: !isValid }"
v-on:blur="validate"
v-on:change="changed"> <-- This is the Prop I want to use
<option v-if="showEmptyOption" value="">{{ emptyOption }}</option>
<option v-for="i in items"
v-bind:value="i.value"
v-bind:selected="i.checked === value"
v-bind:disabled="i.enabled === false">
{{ i.text }}
</option>
</select>
</template>
So in the on-change event, it will call the changed method. That was not working. I then added a special prop to the code file:
Vue.component("drop-list", {
template: "#drop-list-template",
props: {
dataset: { type: Array, required: true },
isRequired: { type: Boolean, required: false, default: false },
emptyOption: { type: String, required: false, default: "*Select an Option *" },
showEmptyOption: { type: Boolean, required: false, default: true },
special: { required: false }
},
data: function () {
return {
items: this.dataset,
isValid: true,
value: ""
}
},
methods: {
validate: function (event) {
var Result = true;
if ((this.isRequired === true) && (this.value === ""))
Result = false;
this.isValid = Result;
return Result;
},
changed: function (event) {
if (this.special) {
AbnormalitiesAndImpressions(); <-- Obviously this works
alert("After");
this.special(); <-- Would want this to run AbnormalitiesAndImpressions
}
}
}
});
And implement it via:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
special="AbnormalitiesAndImpressions">
</drop-list>
Where AbnormalitiesAndImpressions is just dumb right now:
function AbnormalitiesAndImpressions(lstAbs, lstImps) {
alert("Got to here");
}
When I run it, the "Got to here" alert pops up and so does the "After" alert. It then fails because this.special(); is not a function.
Bottom line is I am trying to let the user (myself in this case) create as many of these lists as needed. What will happen on some of them is they tweak what is available in other controls. So a sort of validation is going on. I just want this to be customizable per each use of the component.
I would even be fine with an anonymous function like the following:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
special="function () { AbnormalitiesAndImpressions(); }">
</drop-list>
Update
I have updated my component slightly:
<template id="drop-list-template">
<select class="form-control"
v-model="value"
v-bind:class="{ required: isRequired, invalid: !isValid }"
v-on:blur="validate"
v-on:change="change">
<option v-if="showEmptyOption" value="">{{ emptyOption }}</option>
<option v-for="i in items"
v-bind:value="i.value"
v-bind:selected="i.checked === value"
v-bind:disabled="i.enabled === false">
{{ i.text }}
</option>
</select>
</template>
And the corresponding javascript:
Vue.component("drop-list", {
template: "#drop-list-template",
props: {
dataset: { type: Array, required: true },
isRequired: { type: Boolean, required: false, default: false },
emptyOption: { type: String, required: false, default: "*Select an Option *" },
showEmptyOption: { type: Boolean, required: false, default: true },
special: { type: Function, required: false }
},
data: function () {
return {
items: this.dataset,
isValid: true,
value: ""
}
},
methods: {
change: function (event) {
if (this.special)
this.special();
}
}
});
And the implementation:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
:special="AbnormalitiesAndImpressions">
</drop-list>
And here is the page's Vue code:
var vm = new Vue({
el: "#app",
data: {
Result: {},
Defaults: {},
Errors: [],
Abnormalities: [],
Impressions: []
},
methods: {
AbnormalitiesAndImpressions: function () {
alert("Should get overridden");
}
}
});
vm.AbnormalitiesAndImpressions = function (lstAbs, lstImps) {
alert("Got to here: " + lstAbs + "\n" + lstImps);
}
I found that if I did not add the methods short version of AbnormalitiesAndImpressions that it would give me a Vue warning that the property did not exist. However, the version of AbnormalitiesAndImpressions at the bottom of that file actually runs. I like this as each implementation could change and they should be on the page and not on the component.
When I change the dropdown item, I do get the Got to here message. And of course it has two undefined as the lstAbs and lspImps were not passed in.
New Question
Is it possible then to pass values to my props function? In this case, they can be strings. But if I do the code below...
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
:special="AbnormalitiesAndImpressions('test')">
</drop-list>
When the page loads, the alert is popped right away and Does have the test parameter. And when I actually change the select the alert does not fire at all.
Ok, so let's do it step by step.
Correct method declaration
methods: {
change: function (event) {
if (this.special)
this.special();
}
}
This method declaration isn't good, it's changing the this context, always declare methods with arrow functions or with the shorthand syntax. Anonymous functions declared like this: function () { //... } creates a new this context, and beacuse of that this.special is always undefined. So change it to:
methods: {
change (event) {
if (this.special)
this.special();
}
}
Do it at all methods, it'll avoid a lot of headache. To another anonymous functions, always use arrow functions.
Method passed as prop
About your new question, let me explain what's happening when you set the special prop as AbnormalitiesAndImpressions with vanilla Js to clarify your mind.
Think about a method foo, just like this below:
function foo (string) {
return string;
}
Above we can see the method declaration, in Js is possible to assign a function to a variable, so, if a create a variable a it can be equals to foo, just like it:
let a = foo;
As you can see, I'm passing the function foo to the var a, not the return of the function foo, it's what you do when you set the special property as AbnormalitiesAndImpressions, because of that you can't do this: :special="AbnormalitiesAndImpressions('test')", but, if we look back to my example, one thing we can do, that is:
a('bar');
And it'll return 'bar', so, applying it to Vue, at your component drop-list, where you call the function as this.special you can pass params to the function, did you get it?

Vue.js component model update

Im absolutely new in Vue framework and I need create reusable component with live BTC/LTC/XRP price
For live prices Im using Bitstamp websockets API. Here is example usage with jQuery - run this snippet, is really live.
var bitstamp = new Pusher('de504dc5763aeef9ff52')
var channel = bitstamp.subscribe('live_trades')
channel.bind('trade', function (lastTrade) {
$('p').text(lastTrade.price)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<h3>BTC/USD price</h3>
<p>loading...</p>
As you can see, its really simple. But, I need to use Vue.js component. So I created this, and its also fully functional:
var bitstamp = new Pusher('de504dc5763aeef9ff52')
Vue.component('live-price', {
template: '<div>{{price}}</div>',
data: function () {
return {
price: 'loading...'
}
},
created: function () {
this.update(this)
},
methods: {
update: function (current) {
var pair = current.$attrs.pair === 'btcusd'
? 'live_trades'
: 'live_trades_' + current.$attrs.pair
var channel = bitstamp.subscribe(pair)
channel.bind('trade', function (lastTrade) {
current.price = lastTrade.price
})
}
}
})
new Vue({
el: '.prices'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<section class="prices">
<live-price pair="btcusd"></live-price>
<live-price pair="ltcusd"></live-price>
<live-price pair="xrpusd"></live-price>
</section>
But, there is big BUT. Am I using Vue right way? WHERE IS IDEAL PLACE to run Pusher? In "created" or "mounted" method? In "computed"? In "watch"? Or where? Am i doing it right? I really dont known, I started with Vue ... today :(
Looks pretty good for your first day using Vue! I would just make a few changes.
The component is reaching out and using a global, bitstamp. Generally with components, you want them to be independent, and not reaching out of themselves to get values. To that end, declare the socket as a property that can be passed in to the component.
Likewise, the pair is passed in as a property, but you do not declare it and instead, use current.$attrs.pair to get the pair. But that's not very declarative and makes it harder for anyone else to use the component. Moreover, by making it a property, you can reference it using this.pair.
When using something like a socket, you should always remember to clean up when you are done using it. In the code below, I added the unsubscribe method to do so. beforeDestroy is a typical lifecycle hook to handle these kinds of things.
Computed properties are useful for calculating values that are derived from your components data: the channel you are subscribing to is a computed property. You don't really need to do this, but its generally good practice.
A Vue can only bind to a single DOM element. You are using a class .prices which works in this case because there is only one element with that class, but could be misleading down the road.
Finally, created is an excellent place to initiate your subscription.
console.clear()
var bitstamp = new Pusher('de504dc5763aeef9ff52')
Vue.component('live-price', {
props:["pair", "socket"],
template: '<div>{{price}}</div>',
data() {
return {
price: 'loading...',
subscription: null
}
},
created() {
this.subscribe()
},
beforeDestroy(){
this.unsubscribe()
},
computed:{
channel(){
if (this.pair === 'btcusd')
return 'live_trades'
else
return 'live_trades_' + this.pair
}
},
methods: {
onTrade(lastTrade){
this.price = lastTrade.price
},
subscribe() {
this.subscription = this.socket.subscribe(this.channel)
this.subscription.bind('trade', this.onTrade)
},
unsubscribe(){
this.subscription.unbind('trade', this.onTrade)
this.socket.unsubscribe(this.channel)
}
}
})
new Vue({
el: '#prices',
data:{
socket: bitstamp
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<section id="prices">
<live-price pair="btcusd" :socket="bitstamp"></live-price>
<live-price pair="ltcusd" :socket="bitstamp"></live-price>
<live-price pair="xrpusd" :socket="bitstamp"></live-price>
</section>
Rewrited - is it ok now?
var config = {
key: 'de504dc5763aeef9ff52'
}
var store = new Vuex.Store({
state: {
pusher: null
},
mutations: {
initPusher (state, payload) {
state.pusher = new Pusher(payload.key)
}
}
})
var livePrice = {
template: '#live-price',
props: ['pair'],
data () {
return {
price: 'loading...',
subscription: null
}
},
computed: {
channel () {
return this.pair === 'btcusd'
? 'live_trades'
: 'live_trades_' + this.pair
}
},
methods: {
onTrade (lastTrade) {
this.price = lastTrade.price
},
subscribe () {
this.subscription = this.$store.state.pusher.subscribe(this.channel)
this.subscription.bind('trade', this.onTrade)
},
unsubscribe () {
this.subscription.unbind('trade', this.onTrade)
this.$store.state.pusher.unsubscribe(this.channel)
}
},
created () {
this.subscribe()
},
beforeDestroy () {
this.unsubscribe()
}
}
new Vue({
el: '#prices',
store,
components: {
'live-price': livePrice
},
created () {
store.commit({
type: 'initPusher',
key: config.key
})
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.min.js"></script>
<section id="prices">
<live-price pair="btcusd"></live-price>
<live-price pair="ltcusd"></live-price>
<live-price pair="xrpusd"></live-price>
</section>
<template id="live-price">
<div>
{{price}}
</div>
</template>

Cannot add/remove/toggle element class inside Vue directive?

This works: https://jsfiddle.net/hxyv40ra
However, when I place this code inside of a vue directive the button event is triggered and the console shows the class is removed but nothing visually changes?
Here's an example: https://jsfiddle.net/hLga2jxq
Directive code is also below (to appease stackoverflow's rules).
styles
.hide {
display: none;
}
html
<div id="app">
<button v-hide-for="'uniqueID'">toggle to show?</button>
<div class="hide" hide-name="uniqueID">
Hello! :D
</div>
</div>
js
Vue.directive('hide-for', {
bind(button, b, vnode, oldVnode) {
console.log(b);
var elsToToggle = document.querySelectorAll(`[hide-name="${b.value}"]`);
console.log(button, b.value, `[hide-name="${b.value}"]`, elsToToggle);
button.addEventListener('click', (b) => {
console.log(button, " clicked");
elsToToggle.forEach((el) => {
console.log(el);
el.classList.toggle('hide');
})
}, false)
}
});
var app = new Vue({
name: "test",
el: '#app',
data: {}
})
So I tried this from another angle and also made the 'hide-name' attribute a directive as well, then on click I emitted the 'uniqueID' which 'hide-name' directive picked up.
I'm still not sure why Vue is not visually updating the browser but I'm guessing it must have something to do with the 'virtual-dom'.
demo: https://jsfiddle.net/hLga2jxq/3/
Vue.directive('hide-for', {
bind(el, b, vnode) {
el.addEventListener('click', (event) => vnode.context.$emit(b.value, event) );
}
});
Vue.directive('hide-name', {
bind(el, b, vnode, oldVnode) {
vnode.context.$on(b.value, function(){
let hasHideClassAttr = el.getAttribute('hide-class');
if(hasHideClassAttr) hasHideClassAttr.split(' ').forEach((c) => el.classList.toggle(c) );
else el.classList.toggle('hide');
});
}
});

vue.js: how to handle click and dblclick events on same element

I have a vue component with separate events for click/dblclick. Single click (de)selects row, dblclick opens edit form.
<ul class="data_row"
v-for="(row,index) in gridData"
#dblclick="showEditForm(row,$event)"
#click="rowSelect(row,$event)"
>
Doing it like this, i get 3 events fired on double click. Two click events and lastly one dblclick. Since the click event fires first , is there a way (short of deferring click event for a fixed amount of ms) for stopping propagation of click event on double click ?
Fiddle here
As suggested in comments, You can simulate the dblclick event by setting up a timer for a certain period of time(say x).
If we do not get another click during that time span, go for the single_click_function().
If we do get one, call double_click_function().
Timer will be cleared once the second click is received.
It will also be cleared once x milliseconds are lapsed.
See below code and working fiddle.
new Vue({
el: '#app',
data: {
result: [],
delay: 700,
clicks: 0,
timer: null
},
mounted: function() {
console.log('mounted');
},
methods: {
oneClick(event) {
this.clicks++;
if (this.clicks === 1) {
this.timer = setTimeout( () => {
this.result.push(event.type);
this.clicks = 0
}, this.delay);
} else {
clearTimeout(this.timer);
this.result.push('dblclick');
this.clicks = 0;
}
}
}
});
<div id="example-1">
<button v-on:dblclick="counter += 1, funcao()">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
},
methods: {
funcao: function(){
alert("Sou uma funcao");
}
}
})
check out this working fiddle https://codepen.io/robertourias/pen/LxVNZX
i have a simpler solution i think (i'm using vue-class but same principle apply):
private timeoutId = null;
onClick() {
if(!this.timeoutId)
{
this.timeoutId = setTimeout(() => {
// simple click
}, 50);//tolerance in ms
}else{
clearTimeout(this.timeoutId);
// double click
}
}
it does not need to count the number of clicks.
The time must be short between click and click.
In order to get the click and double click, only one counter is required to carry the number of clicks(for example 0.2s) and it is enough to trap the user's intention when he clicks slowly or when he performs several that would be the case of the double click or default case.
I leave here with code how I implement these features.
new Vue({
el: '#app',
data: {numClicks:0, msg:''},
methods: {
// detect click event
detectClick: function() {
this.numClicks++;
if (this.numClicks === 1) { // the first click in .2s
var self = this;
setTimeout(function() {
switch(self.numClicks) { // check the event type
case 1:
self.msg = 'One click';
break;
default:
self.msg = 'Double click';
}
self.numClicks = 0; // reset the first click
}, 200); // wait 0.2s
} // if
} // detectClick function
}
});
span { color: red }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script>
<div id='app'>
<button #click='detectClick'>
Test Click Event, num clicks
<span>{{ numClicks }}</span>
</button>
<h2>Last Event: <span>{{ msg }}</span></h2>
</div>
I use this approach for the same problem. I use a promise that is resolved either by the timeout of 200ms being triggered, or by a second click being detected. It works quite well in my recent web apps.
<div id="app">
<div
#click="clicked().then((text) => {clickType = text})">
{{clickType}}
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
click: undefined,
clickType: 'Click or Doubleclick ME'
},
methods: {
clicked () {
return new Promise ((resolve, reject) => {
if (this.click) {
clearTimeout(this.click)
resolve('Detected DoubleClick')
}
this.click = setTimeout(() => {
this.click = undefined
resolve('Detected SingleClick')
}, 200)
})
}
}
})
</script>
Working fiddle:
https://jsfiddle.net/MapletoneMartin/9m62Lrwf/
vue Component
// html
<div class="grid-content">
<el-button
   #click.native="singleClick"
   #dblclick.native="doubleClick"
   class="inline-cell">
click&dbclickOnSameElement</el-button>
</div>
// script
<script>
let time = null; // define time be null
export default {
name: 'testComponent',
data() {
return {
test:''
};
},
methods: {
singleClick() {
// first clear time
clearTimeout(time);
time = setTimeout(() => {
console.log('single click ing')
}, 300);
},
  
doubleClick() {
clearTimeout(time);
console.log('double click ing');
}
}
}
</script>
selectedFolder = ''; // string of currently selected item
folderSelected = false; // preview selected item
selectFolder(folder) {
if (this.selectedFolder == folder) {
// double click
this.folderSelected = false;
this.$store.dispatch('get_data_for_this_folder', folder);
} else {
// single click
this.selectedFolder = folder;
this.folderSelected = true;
}
},
#click.stop handles a single click and #dblclick.stop handles double click
<v-btn :ripple="false"
class="ma-0"
#click.stop="$emit('editCompleteGrvEvent', props.item)"
#dblclick.stop="$emit('sendCompleteGrvEvent',props.item)">
<v-icon>send</v-icon>
</v-btn>
Unless you need to do expensive operations on single select, you can rework rowSelect into a toggle. Setting a simple array is going to be a lot faster, reliable, and more straightforward compared to setting up and canceling timers. It won't matter much if the click event fires twice, but you can easily handle that in the edit function.
<template>
<ul>
<li :key="index" v-for="(item, index) in items">
<a
:class="{ 'active-class': selected.indexOf(item) !== -1 }"
#click="toggleSelect(item)"
#dblclick="editItem(item)"
>
{{ item.title }}
</a>
<!-- Or use a checkbox with v-model
<label #dblclick="editItem(item)">
<input type="checkbox" :value="item.id" v-model.lazy="selected" />
{{ item.title }}
</label>
-->
</li>
</ul>
</template>
<script>
export default {
data: function () {
return {
items: [
{
id: 1,
title: "Item 1",
},
{
id: 2,
title: "Item 2",
},
{
id: 3,
title: "Item 3",
},
],
selected: [],
};
},
methods: {
editItem(item) {
/*
* Optionally put the item in selected
* A few examples, pick one that works for you:
*/
// this.toggleSelect(item); // If the item was selected before dblclick, it will still be selected. If it was unselected, it will still be unselected.
// this.selected = []; // Unselect everything.
// Make sure this item is selected:
// let index = this.selected.indexOf(item.id);
// if (index === -1) {
// this.selected.push(item.id);
// }
// Make sure this item is unselected:
// let index = this.selected.indexOf(item.id);
// if (index !== -1) {
// this.selected.splice(index, 1);
// }
this.doTheThingThatOpensTheEditorHere(item);
},
toggleSelect(item) {
let index = this.selected.indexOf(item.id);
index === -1
? this.selected.push(item.id)
: this.selected.splice(index, 1);
},
// For fun, get an array of items that are selected:
getSelected() {
return this.items.filter((item) => this.selected.indexOf(item.id) !== -1);
},
},
};
</script>

paramWatchers is not working

Vue.directive('example', {
params: ['a'],
paramWatchers: {
a: function (val, oldVal) {
console.log('a changed!');
}
}
});
var vm = new Vue({
el: '#app',
data: {
someValue: 1
},
methods: {
change: function () {
this.someValue += 1;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.27/vue.js"></script>
<div id="app">
<div v-example v-bind:a="someValue"></div>
<button type="button" #click="change">Change</button>
{{ someValue }}
</div>
Can someone explain why paramWatchers never get called?
The example is basically the one from the docs but I can't figure out why it's not working.
Thanks!
While previous answer about naming is correct it doesn't solve your issue.
In newest version 1.0.27 of vue.js, which you use, paramWatchers doesn't work.
Here is example with version 1.0.26 where it works https://jsfiddle.net/f9u05755/
And here is with version 1.0.27 where it doesn't https://jsfiddle.net/oz04mztk/
So until they fix this bug in some new release use versions prior to 1.0.27 for this functionality.
You've called it paramWatcher. The docs call it paramWatchers. Add the S.

Categories

Resources