I'm trying to reuse the dialog form but when trying to update Vuelidate says the form is invalid, I'm not sure why. This is my code.
<b-modal size="lg" ref="my-modal" hide-footer :title="modalTitle + ' User'">
<form #submit.prevent="onSubmit" #reset.prevent="onReset">
<b-row>
<b-col>
<b-form-input id="input-name"
v-model="form.name"
#input="$v.form.name.$touch()"
:state="$v.form.name.$dirty ? !$v.form.name.$error : null"
placeholder="Name"
trim></b-form-input>
</b-col>
<b-col>
<b-form-input id="input-email"
v-model="form.email"
#input="$v.form.email.$touch()"
:state="$v.form.email.$dirty ? !$v.form.email.$error : null"
placeholder="Email"
trim></b-form-input>
</b-col>
</b-row>
<b-row v-if="mode === 0">
<b-col>
<b-form-input id="input-password"
v-model="form.password"
#input="$v.form.password.$touch()"
:state="$v.form.password.$dirty ? !$v.form.password.$error : null"
type="password"
placeholder="Password"
trim></b-form-input>
</b-col>
<b-col>
<b-form-input id="input-password-confirm"
v-model="form.password_confirmation"
#input="$v.form.password_confirmation.$touch()"
:state="$v.form.password_confirmation.$dirty ? !$v.form.password_confirmation.$error : null"
type="password"
placeholder="Password Confirmation"
trim></b-form-input>
</b-col>
</b-row>
<b-button v-if="mode === 1"
class="mb-1"
:class="visible ? null : 'collapsed'"
:aria-expanded="visible ? 'true' : 'false'"
aria-controls="collapse-4"
#click="visible = !visible">Change Password
</b-button>
<b-collapse v-model="visible" class="mt-2">
<b-form-input id="input-password-edit"
v-model="form.password_edit"
#input="$v.form.password_edit.$touch()"
:state="$v.form.password_edit.$dirty ? !$v.form.password_edit.$error : null"
type="password"
placeholder="New Password"
trim></b-form-input>
</b-collapse>
<b-row>
<b-col>
<b-form-select :options="roles"
v-model="form.role"
#input="$v.form.role.$touch()"
:state="$v.form.role.$dirty ? !$v.form.role.$error : null"
placeholder="User Role"></b-form-select>
</b-col>
</b-row>
<b-row align-h="end" style="padding-left: 1rem; padding-right: 1rem">
<b-button class="mr-1" #click="cancel" variant="secondary">Cancel</b-button>
<b-button type="submit" variant="primary">Save</b-button>
</b-row>
</form>
</b-modal>
data() {
return {
url: '/users',
form: {
name: '',
email: '',
password: '',
password_confirmation: '',
password_edit: '',
role: ''
},
users: [],
roles: [],
}
},
validations: {
form: {
name: {
required
},
email: {
required,
email
},
password: {
required: requiredIf(this.modalTitle() === 'New')
},
password_confirmation: {
required: requiredIf(this.modalTitle() === 'New'),
sameAsPassword: sameAs('password')
},
role: {
required
},
password_edit: {
required: requiredIf(this.modalTitle() === 'Edit')
}
}
},
methods: {
create() {
this.onReset();
this.mode = 0;
this.$refs['my-modal'].show();
},
edit(item) {
this.onReset();
this.mode = 1;
this.form = _.cloneDeep(item);
this.form.role = this.form.roles[0]['id'];
this.$refs['my-modal'].show();
},
onSubmit() {
this.$v.$touch()
console.log(this.$v.invalid);
if (!this.$v.$invalid) {
if (this.mode === 0) {
this.$inertia.post(this.url, {
name: this.form.name,
email: this.form.email,
password: this.form.password,
password_confirmation: this.form.password_confirmation,
role: this.form.role,
}).then(
() => {
if (Object.entries(this.$page.props.errors).length === 0) {
this.$refs['my-modal'].hide();
this.onReset();
this.$bvToast.toast('New User Created', {
title: 'Action Successful',
variant: 'success',
toaster: 'b-toaster-top-center',
solid: true
});
}
}
)
} else {
console.log('here')
this.$inertia.put(this.url + '/' + this.form.id, {
name: this.form.name,
email: this.form.email,
role: this.form.role,
password_edit: this.form.password_edit
}).then(
() => {
if (Object.entries(this.$page.props.errors).length === 0) {
this.$refs['my-modal'].hide();
this.onReset();
this.$bvToast.toast('User Updated', {
title: 'Action Successful',
variant: 'success',
toaster: 'b-toaster-top-center',
solid: true
});
}
}
)
}
}
},
},
In the onSubmit() if I add a console.log it stops working after if (!this.$v.$invalid) which means that I'm not using Vuelidate correctly but in the validations when I try to use requiredIf I get an error
Error in created hook: "TypeError: Cannot read property 'modalTitle' of undefined"
TypeError: Cannot read property 'modalTitle' of undefined
this.modelTitle() is a computed property, I also tried doing required: requiredIf(this.mode === 1) but I get the same errors.
How can I fix it so that when creating the required fields include the password and password_confimation but when editing don't require them?
The solution is to change the validators like this, in my case to be able to edit a password a section opens up using the variable visible so only when that section is visible it will require the new password, if the section is not visible then the password doesn't require being changed.
validations: {
form: {
password: {
required: requiredIf(function (pass) {
return !this.form.id
}),
},
password_confirmation: {
required: requiredIf(function (pass) {
return !this.form.id
}),
sameAsPassword: sameAs('password')
},
password_edit: {
required: requiredIf(function (pass) {
return this.form.id && this.visible
}),
}
}
},
Related
I am using row-selected on Bootstrap table, so that when a user select a row in a table, the data of that table is passed to a new object which is then sent as a prop to a child component, which is a modal.
The issue is that when I first click on a row, the modal opens and shows the data correctly, but if I close the modal and click the same row again, the data object is empty and nothing gets shown. I need to click it again for a third time to see the data. Then if I click a fourth time, the data is gone again.
I don't understand it, because in my showModal method, I check first to see if selectedRows are empty or not, so it shouldn't open if there was no data.
Any idea why this happens?
Parent:
<template>
<b-container>
<b-card class="mt-4 mb-4">
<h5>{{ $t('errorLogs.errorLog') }}</h5>
<b-table
:items="completedTasks"
:fields="fields"
:per-page="[10, 25, 50]"
selectable
:select-mode="'single'"
#row-selected="onRowSelected"
#row-clicked="showModal"
include-actions
sort-desc
/>
</b-card>
<error-log-entry-modal ref="errorLogEntryModal" :selected-error-log="selectedRows"/>
</b-container>
</template>
<script>
import ErrorLogEntryModal from '#/components/error-log/ErrorLogEntryModal';
export default {
components: {
ErrorLogEntryModal,
},
data() {
return {
errors: null,
tasksCompleted: null,
selectedRows: []
};
},
computed: {
fields() {
return [
{
key: 'done',
label: '',
thStyle: 'width: 1%',
template: {
type: 'checkbox',
includeCheckAllCheckbox: true,
},
},
{
key: 'priority',
label: this.$t('errorLogs.priority'),
formatter: type => this.$t(`model.errors.types.${type}`),
sortable: true,
},
{
key: 'creationDateTime',
label: this.$t('creationDateTime'),
formatter: date => moment(date).locale(this.$i18n.locale).format('L'),
sortable: true,
},
{
key: 'stackTraceShort',
label: this.$t('errorLogs.stackTrace'),
sortable: true,
},
{
key: 'message',
label: this.$t('message'),
sortable: true
},
]
},
completedTasks(){
if(this.errors){
return this.errors.filter(log => log.done)
}
},
},
methods: {
load(){
errorService.getErrorLogs().then(result => {
result.data.forEach(log => log.stackTraceShort = log.stackTrace.substring(0,30));
this.errors = result.data
})
},
submit(){
this.$bvModalExt
.msgBoxYesNo(
this.$t('archiveErrorLog'),
{
title: this.$t('pleaseConfirm'),
okVariant: 'success'
}
).then(value => {
if (value) {
errorService.updateStatusOnErrorEntryLog(this.errors)
}
})
},
onRowSelected(fields){
this.selectedRows = fields
},
showModal(){
if (this.selectedRows) {
this.$refs.errorLogEntryModal.show()
}
},
},
created() {
this.load()
},
};
</script>
child:
<template>
<b-modal
modal-class="error-log-modal"
v-model="showModal"
ok-only
size="xl">
<b-row class="lg-12" v-for="log in selectedErrorLog">
<b-col class="ml-2 mr-2 mb-4">
<h4>{{ $t('errorLogs.errorMessage') }}</h4>
{{ log.message }}
</b-col>
</b-row>
<b-row class="lg-12">
<b-col class="ml-2 mr-2"><h4>{{ $t('errorLogs.stackTrace') }}</h4></b-col>
</b-row>
<b-row class="lg-12" v-for="log in selectedErrorLog">
<b-col class="ml-2 mr-2" style="word-break: break-word; background-color: #F5C9C1;">
{{ log.stackTrace }}
</b-col>
</b-row>
</b-modal>
</template>
<script>
export default {
props: {
selectedErrorLog: Array
},
data() {
return {
showModal: false,
};
},
methods: {
show(){
this.showModal = true
},
}
};
</script>
In my application I have a list of documents that I display in a table, each document has a specific type, which is specified in json file as enum values.
I can already display all the documents without any problems, but now I have tried to create an input where the user can choose from the list of enum values, and when doing so only the documents with the selected enum type will be shown in the table.
This does sorta work, but the problem is that I have somehow created an infinite update loop, which causes the application to randomly stop working.
This is the template. I am using a custom made template component, but that is not related to the issue.
<template>
<b-container>
<div #dragover.prevent.stop #drop.prevent.stop="onDropEvent">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button-toolbar justify>
<b-button
:class="showCollapse ? 'collapsed' : null"
#click="showCollapse = !showCollapse"
variant="outline-info"
class="flex-grow-1"
>
<span class="float-left">{{ folderName }}</span>
<span class="float-right">
{{
search ?
$t('documentCountFiltered', {filtered: filteredDocuments.length, count: documents.length}) :
$tc('documentCount', documents.length)
}}
</span>
</b-button>
<template v-if="!inherited && mode !== 'READ_ONLY'">
<label class="document-uploader btn btn-sm btn-outline-primary ml-2">
<span>
<i class="fas fa-fw fa-file-upload"></i>
{{ $t('uploadFiles') }}
</span>
<input type="file" multiple #change="selectFiles">
</label>
<b-dropdown
v-if="mode !== 'READ_ONLY' && mode !== 'RESTRICTED'"
size="sm" variant="outline-primary" class="ml-2 template-dropdown" no-caret right>
<template slot="button-content">
<i class="fas fa-fw fa-file-medical"></i> {{ $t('createDocument') }}
</template>
<b-dropdown-header v-if="companyTemplates.length !== 0" id="dropdown-header-templates">
{{ $t('companyTemplates') }}
</b-dropdown-header>
<b-dropdown-item v-if="companyTemplates.length !== 0" v-for="template of companyTemplates"
#click="emitWordDocumentCreateSelectEvent(template)">
{{ template.name }} <i v-if="template.freeTextEnabled" class="fa-fw fas fa-paragraph"></i>
</b-dropdown-item>
<b-dropdown-divider
v-if="companyTemplates.length !== 0 && companyFormBuilderTemplates.length > 0"></b-dropdown-divider>
<b-dropdown-header v-if="companyFormBuilderTemplates.length > 0" id="dropdown-header-inheritedDocuments">
{{ $t('formBuilderTemplates') }}
</b-dropdown-header>
<b-dropdown-item
v-for="template of companyFormBuilderTemplates"
#click="emitDocumentCreateSelectEvent(template)">
{{ template.name }}
</b-dropdown-item>
<b-dropdown-divider
v-if="globalTemplates.length !== 0"></b-dropdown-divider>
<b-dropdown-header v-if="globalTemplates.length > 0" id="dropdown-header-globalDocuments">
{{ $t('globalTemplates') }}
</b-dropdown-header>
<b-dropdown-item
v-for="template of globalTemplates"
#click="emitGlobalDocumentCreateSelectEvent(template)">
{{ template.name }}
</b-dropdown-item>
</b-dropdown>
</template>
</b-button-toolbar>
</b-card-header>
<b-form-group class="mt-4">
<w-b-form-select v-model="filteredDocumentType">
<template>
<b-form-select-option :value="null" disabled>-- {{ $t('selectDocumentByType') }} --</b-form-select-option>
</template>
<b-form-select-option :value="'all'">({{ $t('all') }})</b-form-select-option>
<option v-for="documentType in availableDocumentTypes" :key="documentType" :value="documentType">
{{ $t('model.document.types.' + documentType) }}
</option>
</w-b-form-select>
</b-form-group>
<b-collapse v-model="showCollapse">
<common-table
:fields="fields"
:items="filteredDocuments"
primary-key="id"
sort-by="creationDateTime"
sort-desc
>
<template slot="head(select)">
<check-all-checkbox :list="filteredDocuments" #change="handleTag(documents, $event)" property="selected"/>
</template>
<template slot="cell(select)" slot-scope="data">
<w-b-form-checkbox v-model="data.item.selected" v-if="data.item.selected != null"
#change="handleTag([data.item], $event)" data-test-id="check-box"/>
</template>
<template slot="cell(name)" slot-scope="data">
<div class="d-flex">
<div style="flex: 2 0 0">
<b-dropdown :text="data.item.name" variant="link" toggle-class="name-cell">
<b-dropdown-item #click="viewFile(data.item)" v-if="data.item.type != 'EMAIL'"><i
class="fas fa-eye"></i> {{ $t('showDocument') }}
</b-dropdown-item>
<b-dropdown-item #click="downloadFile(data.item)"><i class="fas fa-file-download"></i> {{
$t('download')
}}
</b-dropdown-item>
</b-dropdown>
</div>
<div style="flex: 1 0 0" v-if="data.item.uploading">
<b-progress :animated="!data.item.error" striped class="h-100">
<b-progress-bar
:value="100"
:variant="data.item.error ? 'danger' : 'primary'"
:label="data.item.error ? 'Error' : 'Uploading...'"
/>
</b-progress>
</div>
</div>
</template>
</common-table>
</b-collapse>
<p-d-f-j-s-viewer ref="pdf-viewer"/>
</div>
</b-container>
</template>
My script
<script>
import CheckAllCheckbox from '#/components/CheckAllCheckbox';
import CommonTable from '#/components/common/CommonTable';
import CommonInput from '#/components/common/CommonInput';
import {applianceService} from '#/services/appliance';
import PDFJSViewer from '#/components/PDFJSViewer';
import axios from '#/config/axios';
import {documentService} from '#/services/document';
import documentTypes from '#/models/document/type';
import {propertyFacilityService} from '#/services/property-facility';
import CommonCollapsible from '#/components/common/CommonCollapsible';
export default {
props: {
documents: Array,
documentOwnerType: String,
companyTemplates: Array,
companyFormBuilderTemplates: Array,
globalTemplates: Array,
inherited: Boolean,
startCollapsed: {
type: Boolean,
default: false
},
mode: String,
search: String,
applianceId: String,
propertyFacilityId: String,
noteId:String
},
components: {
CheckAllCheckbox,
CommonTable,
CommonCollapsible,
CommonInput,
PDFJSViewer
},
data() {
return {
documentTypes,
showCollapse: true,
filteredDocumentType: null
};
},
computed: {
filteredDocuments() {
console.log(this.filteredDocumentType)
console.log(this.documents)
if (this.filteredDocumentType === null || this.filteredDocumentType === 'all') {
return (this.documents ?? []).filter(document =>
(document.name.toUpperCase().includes(this.search.toUpperCase()) ||
this.$t(`model.document.types.${document.type}`).toUpperCase().includes(this.search.toUpperCase())))
}
else {
return (this.documents ?? []).filter(document =>
(document.name.toUpperCase().includes(this.search.toUpperCase()) ||
this.$t(`model.document.types.${document.type}`).toUpperCase().includes(this.search.toUpperCase())) &&
document.type === this.filteredDocumentType)
}
},
folderName() {
if (this.inherited) {
return this.$t('sharedDocuments');
} else if (this.documentOwnerType === 'COMPANY') {
return this.$t('companyDocuments');
} else {
return this.$t('documents');
}
},
availableDocumentTypes() {
return this.documentTypes.sort((a, b) => this.getDocumentTypeText(a).localeCompare(this.getDocumentTypeText(b)));
},
fields() {
return [
{
key: 'select',
thStyle: 'width: 1%'
},
{
key: 'name',
label: this.$t('name'),
sortable: true
},
{
key: 'inherited',
label: this.$t('inherited'),
formatter: inherited => this.$t(inherited ? 'yes' : 'no'),
sortable: true,
hide: this.inherited
},
{
key: 'sharedOnTC',
label: this.$t('sharedOnTC'),
formatter: sharedOnTC => this.$t(sharedOnTC ? 'yes' : 'no'),
sortable: true,
hide: this.sharedOnTC
},
{
key: 'type',
label: this.$t('type'),
formatter: type => this.$t('model.document.types.' + type),
sortable: true,
sortByFormatted: true
},
{
key: 'tags',
label: this.$t('tags'),
formatter: tags => tags.join(', '),
sortable: true,
sortByFormatted: true
},
{
key: 'signed',
label: this.$t('signed'),
formatter: sharedOnTC => this.$t(sharedOnTC ? 'yes' : 'no'),
sortable: true,
sortByFormatted: true
},
{
key: 'creationDateTime',
label: this.$t('created'),
sortable: true,
template: {type: 'date', format: 'L LT'}
},
{
key: 'changedDateTime',
label: this.$t('changed'),
sortable: true,
template: {type: 'date', format: 'L LT'}
},
{
key: 'actions',
hide: this.inherited || this.mode === 'READ_ONLY',
template: {
type: 'actions',
cell: [
{
icon: 'fa-edit',
tooltip: this.$t('edit'),
if: this.mode !== 'RESTRICTED',
disabled: data => data.item.uploading || !data.item.documentTemplateId || data.item.signed,
action: data => this.emitDocumentEditTemplateSelectEvent(data.item)
},
{
icon: 'fa-cog',
tooltip: this.$t('documentSettings'),
disabled: data => data.item.uploading,
action: data => this.emitDocumentEditSelectEvent(data.item)
},
{
icon: 'fa-trash',
variant: 'outline-danger',
disabled: data => data.item.uploading,
action: data => this.emitDocumentDeleteSelectEvent(data.item)
}
]
}
}
].filter(field => !field.hide);
}
},
methods: {
getDocumentTypeText(type) {
return this.$t(`model.document.types.${type}`);
},
selectFiles(event) {
this.emitFileUploadEvent(event.target.files);
event.target.value = '';
},
onDropEvent(event) {
this.emitFileUploadEvent(event.dataTransfer.files);
},
downloadFile(document) {
documentService.downloadDocument(document.id)
.catch(error => {
console.error(error);
});
},
viewFile(document) {
documentService.getPublicDownloadToken(document.id).then(result => {
let fileName = `${axios.defaults.baseURL}/file/public/${result.data}/download`;
this.$refs['pdf-viewer'].show(fileName);
}).catch(error => {
console.error(error);
});
},
emitGlobalDocumentCreateSelectEvent(template) {
this.$emit('document-global-create-select', template);
},
emitDocumentCreateSelectEvent(template) {
this.$emit('document-create-select', template);
},
emitWordDocumentCreateSelectEvent(template) {
this.$emit('document-word-create-select', template);
},
emitDocumentEditSelectEvent(document) {
documentService.getDocument(document.id).then(result => {
document = result.data;
this.$emit('document-edit-select', document);
});
},
emitDocumentEditTemplateSelectEvent(document) {
this.$emit('document-edit-template-select', document);
},
emitDocumentDeleteSelectEvent(document) {
this.$emit('document-delete-select', document);
},
emitFileUploadEvent(files) {
if (files && files.length) {
this.$emit('file-upload', [...files]);
}
},
handleTag(documents, selected) {
if (this.applianceId) {
applianceService.updateDocuments(this.applianceId, {
documentIds: documents.map(doc => doc.id), selected: selected
}).then(({data: documents}) => {
documents.forEach(doc => {
this.documents.splice(this.documents.findIndex(d => d.id === doc.id), 1,
Object.assign(doc, {selected: selected}));
});
});
}
if (this.propertyFacilityId) {
propertyFacilityService.updateDocuments(this.propertyFacilityId, {
documentIds: documents.map(doc => doc.id), selected: selected
}).then(({data: documents}) => {
documents.forEach(doc => {
this.documents.splice(this.documents.findIndex(d => d.id === doc.id), 1,
Object.assign(doc, {selected: selected}));
});
});
}
}
},
created() {
this.showCollapse = !this.startCollapsed;
}
};
</script>
I am modifying a module on Prestashop (Blockwishlist) which uses VueJS and I want to add the available combinations in the customers' wish list in order to be able to select a combination and add it to the cart.
But I'm stuck on a part when loading the page: I get the id_product and the id_product_attribute and I also loop on all the variations of the product.
I am looking for a checked radio button by comparing the values of my loop and the id_product_attribute value that I get back and if it exists in my loop this checked the radio button.
This is what my vuejs code looks like now (with attempts that don't work)
<template>
<div class="wishlist-product">
<a
class="wishlist-product-link"
:href="product.canonical_url"
>
<div class="wishlist-product-image">
<img
v-if="product.default_image"
:src="product.default_image.large.url"
:alt="product.default_image.legend"
:title="product.default_image.legend"
:class="{
'wishlist-product-unavailable': !product.add_to_cart_url
}"
>
<img
v-else
:src="prestashop.urls.no_picture_image.bySize.home_default.url"
>
</div>
<div class="wishlist-product-right">
<p class="wishlist-product-title">{{ product.name }}</p>
<p class="wishlist-product-price">
<span
class="wishlist-product-price-promo"
v-if="product.has_discount"
>
{{ product.regular_price }}
</span>
{{ product.price }}
</p>
<div class="wishlist-product-combinations">
<div class="product-variants">
<div class="clearfix product-variants-item">
<div id="group_1" class="radio">
<div v-for="(attribute, key, index) of product.all_attributes" class="input-container float-xs-left"
:class="{'no_stock_variant' : attribute.quantity <= 0}">
<label>
<input
#change="changeAttribute($event)"
v-model="id_product_attribute"
name="id_product_attribute"
class="input-radio" type="radio"
:data-id-product=attribute.id_product
:data-id-product-attribute=attribute.id_product_attribute
:data-product-attribute=attribute.id_attribute
:value="key"
v-if="checkedId(attribute.id_product_attribute) ? 'checked=checked' : ''"
:title=attribute.attribute_name>
<div class="content">
<span class="radio-label">
{{ attribute.attribute_name }}
<img v-if="attribute.quantity <= 0"
:src="imgEnveloppe"
:class="{hover: isHovering}"
#mouseover="imgEnveloppe = imgRing"
#mouseout="isHovering = false"
:alt="attribute.attribute_name"
>
</span>
</div>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</a>
<div class="wishlist-product-bottom">
<button
class="btn btn-indies-gold wishlist-button-add"
v-if="!isShare"
#click="removeFromWishlist"
>
{{ deleteText }}
</button>
<button
class="btn btn-indies wishlist-product-addtocart"
:disabled="isDisabled"
#click="
product.add_to_cart_url || product.customizable === '1'
? addToCartAction()
: null
"
>
{{ product.customizable === '1' ? customizeText : addToCart }}
</button>
</div>
<p
class="wishlist-product-availability wishlist-product-availability-responsive"
v-if="product.show_availability"
>
<i
class="material-icons"
v-if="product.availability === 'unavailable'"
>
block
</i>
<i
class="material-icons"
v-if="product.availability === 'last_remaining_items'"
>
warning
</i>
{{ product.availability_message }}
</p>
</div>
</template>
<script>
import EventBus from '#components/EventBus';
import headers from '#constants/headers';
import prestashop from 'prestashop';
import wishlistAddProductToCartUrl from 'wishlistAddProductToCartUrl';
export default {
name: 'Product',
props: {
product: {
type: Object,
required: true,
default: null,
},
deleteText: {
type: String,
required: true,
},
imgEnveloppe: {
type: String,
required: true,
},
imgRing: {
type: String,
required: true,
},
listId: {
type: Number,
required: true,
default: null,
},
isShare: {
type: Boolean,
required: false,
default: false,
},
customizeText: {
type: String,
required: true,
default: 'Customize',
},
quantityText: {
type: String,
required: true,
default: 'Quantity',
},
addToCart: {
type: String,
required: true,
},
status: {
type: Number,
required: false,
default: 0,
},
hasControls: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
prestashop,
isHovering: false,
id_product_attribute: null
};
},
computed: {
isDisabled() {
if (this.product.customizable === '1') {
return false;
}
return !this.product.add_to_cart_url;
},
},
methods: {
checkedId (idProductAttribute) {
if (this.product.id_product_attribute === parseInt(idProductAttribute)) {
console.log(idProductAttribute);
return true;
}
return false;
},
changeAttribute(e) {
this.id_product_attribute = e.target.getAttribute('data-id-product-attribute');
},
/**
* Remove the product from the wishlist
*/
async removeFromWishlist() {
EventBus.$emit('showDeleteWishlist', {
detail: {
listId: this.listId,
productId: this.product.id,
productAttributeId: this.product.id_product_attribute,
},
});
},
async addToCartAction() {
if (this.product.add_to_cart_url && this.product.customizable !== '1') {
try {
const datas = new FormData();
datas.append('qty', this.product.wishlist_quantity);
datas.append('id_product', this.product.id_product);
datas.append('id_product_attribute', this.id_product_attribute);
datas.append('id_customization', this.product.id_customization);
const response = await fetch(
`${this.product.add_to_cart_url}&action=update`,
{
method: 'POST',
headers: headers.addToCart,
body: datas,
},
);
const resp = await response.json();
prestashop.emit('updateCart', {
reason: {
idProduct: this.product.id_product,
idProductAttribute: this.id_product_attribute,
idCustomization: this.product.id_customization,
linkAction: 'add-to-cart',
},
resp,
});
/* eslint-disable */
const statResponse = await fetch(
`${wishlistAddProductToCartUrl}¶ms[idWishlist]=${this.listId}¶ms[id_product]=${this.product.id_product}¶ms[id_product_attribute]=${this.id_product_attribute}¶ms[quantity]=${this.product.wishlist_quantity}`,
{
headers: {
'Content-Type':
'application/x-www-form-urlencoded; charset=UTF-8',
Accept: 'application/json, text/javascript, */*; q=0.01'
}
}
);
/* eslint-enable */
await statResponse.json();
} catch (error) {
prestashop.emit('handleError', {
eventType: 'addProductToCart',
resp: error,
});
}
} else {
window.location.href = this.product.canonical_url;
}
},
},
};
Thank you for help.
There is a problem that is wasting too much time. I installed the Nuxt js recaptcha module. but the information given in the documentation is insufficient. I haven't used recaptcha before. How exactly should I use it.
<template>
<div class="mx-auto mt-5" style="width: 500px; max-width:90%">
<div class="mx-auto mt-5" style="width: 230px;">
<img
src="#/assets/media/images/site/logo.png"
style="width: 110px"
/>.com'a Üye Olun
</div>
<div class="bg-white p-4 mt-2" style="border-radius:20px">
<b-form #submit.prevent="onSubmit" #reset="onReset" v-if="show">
<b-form-group id="input-group-2" label-for="input-2">
<b-form-input
id="input-2"
class="form-control form-control-lg"
v-model="form.userFullName"
placeholder="İsim soyisim"
required
></b-form-input>
</b-form-group>
<b-form-group id="input-group-2" label-for="input-2">
<b-form-input
id="input-5"
class="form-control form-control-lg"
v-model="form.userName"
placeholder="Kullanıcı adı"
required
></b-form-input>
</b-form-group>
<b-form-row>
<b-col>
<b-form-input
id="input-1"
v-model="form.userEmail"
type="email"
class="form-control form-control-lg"
placeholder="E-mail adresiniz"
required
></b-form-input>
</b-col>
<b-col>
<b-form-input
id="input-3"
v-model="form.userPassword"
class="form-control form-control-lg"
placeholder="Şifreniz"
required
></b-form-input>
</b-col>
</b-form-row>
<b-form-group
id="input-group-4"
class="mt-3"
v-slot="{ ariaDescribedby }"
>
<b-form-checkbox-group
v-model="form.checked"
id="checkboxes-4"
:aria-describedby="ariaDescribedby"
>
<b-form-checkbox class="text-dark" value="1"
>Beni Hatırla</b-form-checkbox
>
</b-form-checkbox-group>
</b-form-group>
<b-button
:disabled="isClickSubmit"
type="submit"
class="btn btn-dark btn-lg btn-block"
variant="primary"
>
<b-spinner v-if="isClickSubmit" small style="margin-bottom:3px" type="grow"></b-spinner>
Kaydol</b-button
>
</b-form>
</div>
</div>
</template>
import axios from "axios";
export default {
layout: "default",
data() {
return {
isClickSubmit: false,
form: {
userEmail: "",
userFullName: "",
userName: "",
userPassword: null
},
show: true
};
},
methods: {
async mounted() {
try {
const bune = await this.$recaptcha.init();
console.log(bune);
} catch (e) {
console.log(e);
}
},
async onSubmit(event) {
this.isClickSubmit = true;
this.onReset();
try {
console.log(this.$recaptcha);
const token = await this.$recaptcha.execute("login");
console.log("ReCaptcha token:", token);
// await this.$recaptcha.reset()
const form = this.form;
const sonuc = await axios.post("http://localhost:3000/api/users", {
form
});
this.isClickSubmit = false
} catch (error) {
console.log("Login error:", error);
}
// console.log(JSON.stringify(this.form));
},
onReset() {
this.form.userEmail = "";
this.form.userFullName = "";
this.form.userName = "";
this.form.userPassword = null
}
}
};
nuxt.config.js:
env: {
GOOGLE_SECRET: '...' },
privateRuntimeConfig: {
secretKey: process.env.GOOGLE_SECRET },
modules: [
[
"#nuxtjs/recaptcha",
{
siteKey:process.env.GOOGLE_SECRET ,
version: 3,
} ]
],
You don't seem to have the recaptcha element in your template.
<!-- Add this where you want the captcha, regardless of version -->
<recaptcha #error="onError" #success="onSuccess" #expired="onExpired" />
<script>
export default {
data() {
return {
isClickSubmit: false,
form: {
userEmail: "",
userFullName: "",
userName: "",
userPassword: null,
token: null
},
show: true
};
},
methods: {
onSuccess(token) {
this.form.token = token;
},
onExpired() {
this.$recaptcha.reset();
},
onError(error) {
console.error(error);
}
}
}
Before you make your request, you'll need to send some things to Google. You'll make this call before serving any requests. This function is from a project of mine.
// Backend code
function Recaptcha(token, ip, callback) {
axios.post(`https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${token}`,
{
remoteip: ip,
},
{
headers: {
'Content-Type':
'application/x-www-form-urlencoded; charset=utf-8',
},
},
)
.then(callback);
}
Example usage of Recaptcha function:
Hopefully this helps you understand it a bit better.
Struggling to sort out how to get a selected value from a Typeahead component to pass back to the parent component. I'm allowing the user to search from a variety of data to link a record to a post. Once the user clicks one of the typeahead drop-down records, I pass the item to the sendlink method - I've checked that the data passes ok. When I do the emit using the selected-link event, I'm not getting the data in the parent component.
PostList.vue
<template>
<div>
<div v-if='posts.length === 0' class="header">There are no posts yet!</div>
<form action="#" #submit.prevent="createPost()" class="publisher bt-1 border-fade bg-white" autocomplete="off">
<div class="input-group">
<input v-model="post.content" type="text" name="content" class="form-control publisher-input" placeholder="What's the lastest?" autofocus>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">Post</button>
</span>
</div>
<span class="publisher-btn file-group">
<i class="fa fa-camera file-browser"></i>
<input type="file">
</span>
</form>
<div #click="doit" v-on:selected-link="onSelectedLink">{{ modellink.name }}</div>
<typeahead
source="/api/typeahead"
placeholder="Link Post to Trip, Business, etc"
filter-key="title"
:start-at="3">
</typeahead>
<post v-for="post in posts"
:key="post.id"
:post="post"
#post-deleted="deletePost($event)">
</post>
</div>
</template>
<script>
var axios = require("axios");
import post from './PostItem.vue';
import typeahead from './Typeahead.vue';
export default {
components: {
post,
typeahead
},
props: ['postableId', 'postableType', 'model'],
data: function() {
return {
modellink: {
"name": "n/a",
"description": "",
"id": null,
"model": "n/a"
},
post: {
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
},
posts: [
{
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
}
]
};
},
created() {
this.fetchPostsList();
},
methods: {
onSelectedLink: function (talink) {
alert(JSON.stringify(talink, null, 4));
this.link = talink
},
doit() {
alert(JSON.stringify(this.modellink, null, 4));
},
fetchPostsList() {
if( this.postableId ) {
axios.get('/api/' + this.postableType + '/' + this.postableId + '/posts').then((res) => {
this.posts = res.data;
});
} else {
axios.get('/api/post').then((res) => {
//alert(JSON.stringify(res.data[0], null, 4));
this.posts = res.data;
});
}
},
createPost() {
axios.post('api/post', {content: this.post.content, user_id: Laravel.userId, vessel_id: Laravel.vesselId })
.then((res) => {
this.post.content = '';
// this.post.user_id = Laravel.userId;
// this.task.statuscolor = '#ff0000';
this.edit = false;
this.fetchPostsList();
})
.catch((err) => console.error(err));
},
deletePost(post) {
axios.delete('api/post/' + post.id)
.then((res) => {
this.fetchPostsList()
})
.catch((err) => console.error(err));
},
}
}
</script>
Typeahead.vue
<template>
<div>
<input
v-model="query"
#blur="reset"
type="text"
class="SearchInput"
:placeholder="placeholder">
<transition-group name="fade" tag="ul" class="Results">
<li v-for="item in items" :key="item.id">
<span #click="sendlink(item)">
<strong>{{ item.name }}</strong> - <small>{{ item.model }}</small><br>
<small>{{ item.description }}</small>
</span>
</li>
</transition-group>
<p v-show="isEmpty">Sorry, but we can't find any match for given term :( </p>
</div>
</template>
<script>
var axios = require("axios");
export default {
name: 'Typeahead',
props: {
modellink: {
type: Object,
required: false
},
source: {
type: [String, Array],
required: true
},
filterKey: {
type: String,
required: true
},
startAt: {
type: Number,
default: 3
},
placeholder: {
type: String,
default: ''
}
},
data() {
return {
items: [],
query: '',
taitem: ''
}
},
computed: {
lookup() {
if(this.query.length >= this.startAt) {
axios.get(this.source + '/' + this.query).then((res) => {
this.items = res.data;
return res.data;
});
}
},
isEmpty() {
if( typeof this.lookup === 'undefined' ) {
return false
} else {
return this.lookup.length < 1
}
}
},
methods: {
sendlink: function (taitem) {
this.$emit('selected-link', taitem);
},
reset() {
this.query = ''
}
}
}
</script>
In your PostList.vue, move the v-on:selected-link="onSelectedLink" from the div to typeahead like below. When emitting an event from child to parent, the listener on the parent needs to be on the child component tag for it to work.
<div #click="doit">{{ modellink.name }}</div>
<typeahead
source="/api/typeahead"
placeholder="Link Post to Trip, Business, etc"
filter-key="title"
:start-at="3"
v-on:selected-link="onSelectedLink">
</typeahead>