I'm trying to avoid letting users submit stripe form when inputs are empty, I`m using stripe.js elements integration to render my form and handle form submition inside my vue component.
this.cardNumberElement.on('change', this.enableForm);
this.cardExpiryElement.on('change', this.enableForm);
this.cardCvcElement.on('change', this.enableForm);
After checking the docs I tried to use the change event on inputs but this is not working sice the user can just not type anything and click submit button.
This is my component:
mounted()
{
console.log(this.$options.name + ' component succesfully mounted');
this.stripe = Stripe(this.stripePK);
this.elements = this.stripe.elements();
this.cardNumberElement = this.elements.create('cardNumber', {style: this.stripeStyles});
this.cardNumberElement.mount('#card-number-element');
this.cardExpiryElement = this.elements.create('cardExpiry', {style: this.stripeStyles});
this.cardExpiryElement.mount('#card-expiry-element');
this.cardCvcElement = this.elements.create('cardCvc', {style: this.stripeStyles});
this.cardCvcElement.mount('#card-cvc-element');
let stripeElements = document.querySelectorAll("#card-number-element, #card-expiry-element, #card-cvc-element");
stripeElements.forEach(el => el.addEventListener('change', this.printStripeFormErrors));
this.cardNumberElement.on('change', this.enableForm);
this.cardExpiryElement.on('change', this.enableForm);
this.cardCvcElement.on('change', this.enableForm);
},
methods:
{
...mapActions('Stripe', ['addSource', 'createSourceAndCustomer']),
...mapMutations('Stripe', ['TOGGLE_PAYMENT_FORM']),
...mapMutations('Loader', ['SET_LOADER', 'SET_LOADER_ID']),
enableForm:function(event){
if(event.complete){
this.disabled = false;
}
else if(event.empty){
this.disabled = true;
}
},
submitStripeForm: function()
{
this.SET_LOADER({ status:1, message: 'Procesando...' });
var self = this;
this.stripe.createSource(this.cardNumberElement).then(function(result) {
if (result.error) {
self.cardErrors = result.error.message;
}
else {
self.stripeSourceHandler(result.source.id);
}
});
},
stripeSourceHandler: function(sourceId)
{
console.log('stripeSourceHandler');
this.cardNumberElement.clear();
this.cardExpiryElement.clear();
this.cardCvcElement.clear();
if(this.customerSources.length == 0)
{
console.log('createSourceAndCustomer');
this.createSourceAndCustomer({ id: sourceId });
}
else
{
console.log('addSource');
this.addSource({ id: sourceId });
}
},
printStripeFormErrors: function(event)
{
if(event.error)
{
self.cardErrors = event.error.message
}
else
{
self.cardErrors = '';
}
}
}
Given the stripe docs, the use of the event seems correct (though it can be improved a bit with using this.disabled = !event.complete to cover error case and not only empty case).
You may try to console.log in the event callback enableForm to check if event is well fired.
Anyway, it's more likely coming from the disabling logic of the submit button and it misses in your post. I've created below a fake secure-component that triggers a change event when value change.
The interesting part in on the container component :
Submit is disabled by default through data disabled,
Submit is enabled if event received has a property complete set to true. If false, it is disabled.
Hope it will help you to focus your trouble.
/**
Mock component to emulate stripes card element behavior with change event
*/
const SecureInput = {
template: '<input type="text" v-model="cardnumber"/>',
data: () => ({
cardnumber: null
}),
watch: {
cardnumber: function(val) {
if(!val) {
this.$emit('change', {empty: true, error: false, complete: false});
return;
}
if(val.length < 5) {
this.$emit('change', {empty: false, error: true, complete: false});
return;
}
this.$emit('change', {empty: false, error: false, complete: true});
}
}
}
/* Logic is here */
const app = new Vue({
el: '#app',
components: {
SecureInput
},
data: {
disabled: true
},
methods: {
updateDisable: function(event) {
this.disabled = !event.complete;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form #submit.prevent="$emit('submitted')">
<p><secure-input #change="updateDisable"/></p>
<p><input type="submit" :disabled="disabled"/></p>
</form>
</div>
Related
I have this logic on changing radio-button selection, if the user made some changing I am showing a message. if he confirm it will enter Onconfirm, else - Onreject.
1 issue -> the change of the radio button happens before the message show.
2 issue -> one reject I want to cancel the choice he made and to undo to his last choise - whice not happenning.
please help me with this!!
radio button
<div class="right" *ngFor="let type of types">
<p-radioButton name="treesDetailsType" [(ngModel)]="oneselectedType" formControlName="selectedType" (onClick)="onChangeType(type,$event)" class="treeDetails" value="{{type.id}}" label="{{type.desc}}" [disabled]="isReadOnly && type.id != data.selectedType"></p-radioButton>
</div>
the function of onclick
onChangeType(type, $event) {
let isFormTouched = this.isFormTouched(type);
if (isFormTouched) {
this.messagingService.showConfirmById(44, () => {
this.onConfirm()
}, () => {
this.onReject($event);
});
}
else
this.onchangedTrue(type); //this set some validators for the choice
}
on reject
#HostListener('click', ['$event']) onReject($event) {
event.stopImmediatePropagation();
//whatever written here its not happens before the change !!!!!
console.log(event);
}
----edited after the perfect suggestion from #Eliseo
askConfirm(value: any) {
let isFormTouched = this.isFormTouched(value);
if (isFormTouched) {
this.messagingService.showConfirmById(44, () => {
this.oneselectedType = value;
this.fg.controls.selectedType.setValue(value);
}, () => {
this.radios.forEach(x => {
x.writeValue(this.oneselectedType);
})
},
);
}
else {
this.oneselectedType = value;
this.onchangedTrue(value);
}
}`
the code work perfectly without the condition
--edited - on get the value from the server and patch it - the radio button lost
There a problem in my code (the another answer). Really I'm not pretty sure the reason, so I create a function like
redraw()
{
const value = this.form.value.type;
this.radios.forEach((x) => {
x.writeValue(value)
});
}
So, my function "ask" becomes like
ask(value: any) {
this.confirmationService.confirm({
message: 'Do you want to choose ' + value + '?',
header: 'Choose Confirmation',
icon: 'pi pi-info-circle',
key: 'positionDialog',
accept: () => {
this.form.get('type').setValue(value);
},
reject: () => {
this.redraw()
},
});
}
This allow me, when change the form, call to the function redraw. If I has a function
getForm(data: any = null) {
data = data || { type: 1, prop: '' };
return new FormGroup({
type: new FormControl(data.type),
prop: new FormControl(data.prop),
});
}
I can do some like
loadData(id: number) {
this.dataService.getData(id).subscribe((res: any) => {
this.form = this.getForm(res);
//it's necesary call to the function this.redraw
this.redraw()
});
}
newData() {
this.form = this.getForm();
//it's necesary call to the function this.redraw
this.redraw()
}
See in the this stackblitz what happens if we don't call to this.redraw() (just comment the lines)
1.-Select "new York" and say that you don't want it
2.-Click the button to load user
As "user" has the type 3 -"new York", the radio buttons looks like that it's not selected.
Yes is an ugly work-around, but for now I can not imagine another solution
Well there're another approach, that is change the value as usually and if we say that we want not the value, return the old value
askAfterChange(value:any)
{
const oldValue=this.form2.value.type;
this.form2.get('type').setValue(value)
this.confirmationService.confirm({
message: 'Do you want to choose ' + value + '?',
header: 'Choose Confirmation',
icon: 'pi pi-info-circle',
key: 'positionDialog',
accept: () => {
},
reject: () => {
this.form2.get('type').setValue(oldValue);
},
});
}
The "key" is split the [(ngModel)] in [ngModel] and (ngModelChanged)
//NOT WORK yet
<p-radioButton ... [ngModel]="selectedType"
(ngModelChange)="askConfirm($event)">
askConfirm(value: any) {
this.confirmationService.confirm({
message: 'Are you sure do you want '+value+'?',
header: 'Delete Confirmation',
icon: 'pi pi-info-circle',
accept: () => {
this.selectedType=value
},
reject: () => {
},
key: "positionDialog"
});
}
Well the problem is that the element still show the value selected How resolved? The first is get our p-radio buttons using ViewChildren, so we are give a template reference variable (the same to all the buttons) see the #radio
<div *ngFor="let type of types" class="p-field-radiobutton">
<p-radioButton #radio ...
(ngModelChange)="ask($event)"
[ngModel]="oneselectedType" ></p-radioButton>
</div>
//get the "radio buttons"
#ViewChildren('radio', { read: RadioButton }) radios!: QueryList<RadioButton>
constructor(private confirmationService: ConfirmationService) { }
ask(value: any) {
this.confirmationService.confirm({
message: 'Do you want to choose this?',
header: 'Choose Confirmation',
icon: 'pi pi-info-circle',
key: 'positionDialog',
accept: () => {
//if accept
this.oneselectedType = value
},
reject: () => {
//else, we loop over all the "radios"
this.radios.forEach(x => {
//and force is checked
x.writeValue(this.oneselectedType);
})
}
});
}
If you're using reactive Forms, you can also use a [ngModel] (ngModelChange) in the way, see that the model is myForm.get('selectedType').value
<p-radioButton ... [ngModel]="myForm.get('selectedType').value"
(ngModelChanged)="askConfirm($event)"
[ngModelOptions]="{standalone:true}"
>
And change in askConfirm
askConfirm(value: any) {
this.confirmationService.confirm({
...
accept: () => {
this.form.get('oneselectedType').setValue(value)
},
reject: () => {
this.radios.forEach(x => {
//and force is checked
x.writeValue(this.form.value.oneselectedType);
})
},
key: "positionDialog"
});
}
a simple stackblitz
Well, In the stackblitz I hard-code the value of the formGroup. Generally we has a service so we can
1.-Define our Form
form=new FormGroup({
selectedCity:new FormControl(),
selectedColor:new FormControl(),
prop:new FormControl()
})
//And in ngOnInit
this.dataService.getData().subscribe(res=>{
this.form.patchValue(res)
})
Or 2.-simple declare our form
form:FormGroup
//and in ngOnInit
use in ngOnInit
this.dataService.getData().subscribe(res=>{
this.form=new FormGroup({
selectedCity:new FormControl(res.selectedCity),
selectedColor:new FormControl(res.selectedColor),
prop:new FormControl(res.prop)
})
})
If we need a default value, we can give the value at first
(the stackblitz has in code this options)
I'm building a Grapesjs plugin and have added a 'jscript' trait to a button component, which appears as a codemirror textarea. The idea is for users to be able to edit some javascript code associated with a button. I can't seem to intercept the codemirror area's change event, at least, not the proper codemirror specific version.
Happily, when I edit the codemirror area and change focus, a regular 'change' event triggers the Grapejs onEvent handler within my plugin's editor.TraitManager.addType('jcodemirror-editor', {} - good. I can then store the contents of the codemirror area into the trait.
onEvent({ elInput, component, event }) {
let code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
component.getTrait('jscript').set('value', code_to_run);
},
However if we paste or backspace or delete etc. in the codemirror area then the regular 'change' event is never issued!
So I'm trying to intercept the deeper codemirror specific 'change' event which is usually intercepted via cm.on("change", function (cm, changeObj) {} and which is triggered more reliably (unfortunately also on each keystroke). How do I wire this codemirror specific event to trigger the usual onEvent({ elInput, component, event }) {} code?
I have a workaround in place in my https://jsfiddle.net/tcab/1rh7mn5b/ but would like to know the proper way to do this.
My Plugin:
function customScriptPlugin(editor) {
const codemirrorEnabled = true // otherwise trait editor is just a plain textarea
const script = function (props) {
this.onclick = function () {
eval(props.jscript)
}
};
editor.DomComponents.addType("customScript", {
isComponent: el => el.tagName == 'BUTTON' && el.hasAttribute && el.hasAttribute("data-scriptable"),
model: {
defaults: {
traits: [
{
// type: 'text',
type: 'jcodemirror-editor', // defined below
name: 'jscript',
changeProp: true,
}
],
script,
jscript: `let res = 1 + 3; console.log('result is', res);`,
'script-props': ['jscript'],
},
},
});
editor.TraitManager.addType('jcodemirror-editor', {
createInput({ trait }) {
const el = document.createElement('div');
el.innerHTML = `
<form>
<textarea id="myjscript" name="myjscript" rows="14">
</textarea>
</form>
</div>
`
if (codemirrorEnabled) {
const textareaEl = el.querySelector('textarea');
var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
mode: "javascript",
lineWrapping: true,
});
// This is the 'more accurate' codemirror 'change' event
// which is triggered key by key. We need it cos if we paste
// or backspace or delete etc. in codemirror then the
// regular 'change' event is never issued! But how do we get
// this event to trigger the proper, usual 'onEvent' below?
// Currently cheating and doing the onEvent work here with
// this special handler.
myCodeMirror.on("change", function (cm, changeObj) { // HACK
const component = editor.getSelected()
const code_to_run = myCodeMirror.getValue()
component.getTrait('jscript').set('value', code_to_run);
console.log('onEvent hack - (myCodeMirror change event) updating jscript trait to be:', code_to_run)
})
}
return el;
},
// UI textarea & codemirror 'change' events trigger this function,
// so that we can update the component 'jscript' trait property.
onEvent({ elInput, component, event }) {
let code_to_run
if (codemirrorEnabled)
code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
else
code_to_run = elInput.querySelector('textarea').value
console.log('onEvent - updating jscript trait to be:', code_to_run)
component.getTrait('jscript').set('value', code_to_run);
}, // onEvent
// Updates the trait area UI based on what is in the component.
onUpdate({ elInput, component }) {
console.log('onUpdate - component trait jscript -> UI', component.get('jscript'))
if (codemirrorEnabled) {
const cm = elInput.querySelector(".CodeMirror").CodeMirror
cm.setValue(component.get('jscript'))
// codemirror content doesn't appear till you click on it - fix with this trick
setTimeout(function () {
cm.refresh();
}, 1);
}
else {
const textareaEl = elInput.querySelector('textarea');
textareaEl.value = component.get('jscript')
// actually is this even needed as things still update automatically without it?
// textareaEl.dispatchEvent(new CustomEvent('change'));
}
}, // onUpdate
}) // addType
editor.BlockManager.add(
'btnRegular',
{
category: 'Basic',
label: 'Regular Button',
attributes: { class: "fa fa-square-o" },
content: '<button type="button">Click Me</button>',
});
editor.BlockManager.add(
'btnScriptable',
{
category: 'Scriptable',
label: 'Scriptable Button',
attributes: { class: "fa fa-rocket" },
content: '<button type="button" data-scriptable="true">Run Script</button>',
});
}
const editor = grapesjs.init({
container: '#gjs',
fromElement: 1,
height: '100%',
storageManager: { type: 0 },
plugins: ['gjs-blocks-basic', 'customScriptPlugin']
});
According to official Grapesjs documentation on traits integrating external ui components you can trigger the onEvent event manually by calling this.onChange(ev).
So within createInput I continued to intercept the more reliable myCodeMirror.on("change", ... event and within that handler triggered the onEvent manually viz:
editor.TraitManager.addType('jcodemirror-editor', {
createInput({ trait }) {
const self = this // SOLUTION part 1
const el = document.createElement('div');
el.innerHTML = `
<form>
<textarea id="myjscript" name="myjscript" rows="14">
</textarea>
</form>
</div>
`
if (codemirrorEnabled) {
const textareaEl = el.querySelector('textarea');
var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
mode: "javascript",
lineWrapping: true,
});
myCodeMirror.on("change", function (cm, changeObj) {
self.onChange(changeObj) // SOLUTION part 2
})
}
return el;
},
I'm trying to clear up the form in the child component after the event containing the entered form data has been successfully passed from the child to parent component. However, I notice that the form gets cleared before the data gets propagated via the event to the parent component, such that the event passes empty values to the parent. I tried delaying the clearForm() using a timeout, but it didn't help. Is there a way to modify the behavior such that the clearForm() happens only after the event completes and the data has been saved?
Attached is the code.
Child Component
<template>
<!-- Contains a form -- >
</template>
<script>
export default {
data() {
return {
additionalInfo:
{
id: new Date().toISOString(),
fullName: '',
preAuthorize: '',
serviceAddress: ''
},
validation: {
fullNameIsValid: true,
serviceAddressIsValid: true
},
formIsValid: true,
addServiceButtonText: '+ Add Service Notes (Optional)',
serviceNotes: [],
showServiceNotes: false,
enteredServiceNote: '', //service notes addendum
}
},
computed : {
// something
},
methods: {
setServiceNotes(){
this.showServiceNotes = !this.showServiceNotes;
},
addAnotherParty(){
this.validateForm();
if(!this.formIsValid){
return;
}
this.$emit('add-parties', this.additionalInfo); //event
console.log(this.clearForm);
},
clearForm(){
this.additionalInfo.fullName = '';
this.additionalInfo.serviceAddress = '';
this.additionalInfo.preAuthorize = false;
}
}
}
</script>
Parent Component
<template>
<div>
<base-card
ref="childComponent"
#add-parties="updateAdditionalInfoList">
<!-- Wrapper for the `Parties Being Served` component-->
<template v-slot:title>
<slot></slot>
</template>
</base-card>
</div>
</template>
<script>
export default {
data() {
return {
hasElement: false,
selectedComponent: 'base-card',
additionalInfoList : [],
clearForm: false
}
},
methods: {
updateAdditionalInfoList(additionalInfo){ //save changes passed via event
this.additionalInfoList.push(additionalInfo);
console.log('emitted');
console.log(this.additionalInfoList);
setTimeout(() => {
this.$refs.childComponent.clearForm(); //clear the form in child
}, 2000);
}
}
}
</script>
Try this
addAnotherParty(){
this.validateForm();
if(!this.formIsValid){
return;
}
let emitObj = JSON.parse(JSON.stringify(this.additionalInfo));
this.$emit('add-parties', emitObj); //event
console.log(this.clearForm);
}
If your object is not deep then you can use
let emitObj = Object.assign({}, this.additionalInfo);
instead of stringify and parse
I am trying to implement common chat app on Vue.js.
window.onload = function () {
new Vue({
el: '#vue-chat',
data: {
body: ''
},
methods: {
fooMethod: function () {
alert('foo');
},
barMethod: function () {
alert('bar');
}
}
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.3/vue.js"></script>
<div id="vue-chat">
<ul class="comments">
<li></li>
</ul>
<input type="text" v-model="body" #keyup.enter="fooMethod">
</div>
and i want to call barMethod when users press enter key and shift key at the same time.
I read docs however I could not find the way.
Thank you for reading!
With the shift key and other modifier keys you can see if they were pressed through the event object.
I'd use a single method with #keyup.enter and then decide to which method to call based on the event's shiftKey value.
new Vue({
el: '#app',
data: {
message: 'Hi',
},
methods: {
action(event) {
if (event.shiftKey) {
this.shiftKeyPressed()
} else {
this.shiftKeyNotPressed()
}
},
shiftKeyPressed() {
console.log('Shift key was pressed.')
},
shiftKeyNotPressed() {
console.log('Shift key was NOT pressed.')
},
}
})
Here's a quick demo: https://jsfiddle.net/bj75cyd3/
There is no trivial way to do what you want.
You can't reach your goal through modifiers; you have to drop the .enter modifier and deal with the keyup event, as well as the keydown event.
<input type="text" v-model="body" #keyup="keyUp" #keydown="keyDown">
There are a short answer and a long answer suggesting how to track multiple keys pressed at once in JavaScript.
Based on the answers linked above, we can build the basis of our Vue solution:
data: {
shiftPressed: false
},
methods: {
keyDown: function (event) {
if (event.keyCode === 16) {
this.shiftPressed = true
}
},
keyUp: function(event) {
if (event.keyCode === 16) {
this.shiftPressed = false
}
if (this.shiftPressed && (event.keyCode === 13)) {
this.shiftPressed = false // avoid double trigger
this.fooMethod()
}
}
}
Is it possible to add an error message for an element in a custom validation function?
HTML:
<form action="#">
<div id="appDiv">
<input name="nEle" type="hidden" value="validate" />
</div>
<br/>
<input name="nEle1" />
<br/>
<input name="nEle2" />
<br/>
<input name="nEle3" />
<br/>
<input name="nEle4" />
<br/>
<br/>
<input type="submit" />
</form>
JQuery:
(function (window, $) {
function Plugin(ele, params) {
return this;
};
Plugin.prototype = {
isValid: function () {
/* After some validation, suppose this raises error and returns false */
return false;
},
getErrors: function () {
/* The validation logic in "isValid" stores the error in the plugin context and this function gets the errors form it and returns */
return "Error evaluated in plugin after its own validation.";
}
}
$.fn.plugin = function (params) {
var retval = this,
initlist = this;
initlist.each(function () {
var p = $(this).data("plugindata");
if (!p) {
$(this).data('plugindata', new Plugin(this, params));
} else {
if (typeof params === 'string' && typeof p[params] === 'function') {
retval = p[params]();
initlist = false;
}
}
});
return retval || initlist;
};
})(window, jQuery);
$.validator.addMethod("customValidation", function (value, element, jqPlugin) {
if (!jqPlugin.plugin('isValid')) {
var errorString = jqPlugin.plugin('getErrors');
console.log("Error String : %s", errorString);
alert("How to set this as error : " + errorString);
/* How to display the error informaiton which is in the errorString ? */
return false;
}
return true;
}, "Default custom validation message.");
$(document).ready(function () {
var jqPlugin = $('#appDiv').plugin();
$('form').validate({
ignore: [],
onkeyup: false,
onclick: false,
onfocusout: false,
rules: {
nEle: {
required: true,
customValidation: jqPlugin
},
nEle1: {
required: true,
},
nEle2: {
required: true,
},
nEle3: {
required: true,
},
nEle4: {
required: true,
},
},
messages: {
nEle: {
customValidation: "Fix error with custom validation."
}
},
submitHandler: function (form) {
alert("Validaton Success..!!");
return false;
}
});
});
JQuery - A probable fix, only deltas :
$.validator.addMethod("customValidation", function (value, element, jqPlugin) {
if (!jqPlugin.plugin('isValid')) {
var errorString = jqPlugin.plugin('getErrors');
console.log("Error String : %s", errorString);
alert("How to set this as error : " + errorString);
/* How to display the error informaiton which is in the errorString ? */
this.errorList.push({
message: errorString,
element: element
});
this.errorMap[$(element).attr('name')] = status.error;
// return false;
}
return true;
}, "Default custom validation message.");
CSS :
div {
width: 150px;
height: 150px;
border: 1px solid black;
margin-bottom: 10px;
}
jsfiddle:
http://jsfiddle.net/m8eEs - Original Code
http://jsfiddle.net/m8eEs/1/ - Updated for clarity
http://jsfiddle.net/m8eEs/2/ - A probable fix which I'm still not happy with, not sure if this is the only way to do it..!!
In the above, I would like to add error message in the function "customValidation" for the element "nEle".
Edited: Maybe its better to add the reason for this kind of question. (directly copied from the comments below)
I know that. But I need to add the error message inside the function. The reason is, element creation & validation(application specific validation, not only the supported required/number/range..) logic is done in a separate plugin(say 'X'). And a set of APIs is exposed to get the validation status and the errors if any. But this element is grouped along with other elements that are validated through the 'validation' plugin. So, in a nutshell, the "customValidation" function just calls the APIs from the 'X' plugin and get the validation status & error messages if any, but stuck with showing it.