Lightgallery can be opened only once (VueJs) - javascript

I have this problem, that lightgallery can be opened only once. Each next click on button is unresponsive. I'm using lightgallery as component.
In my parent component I have two buttons, for opening image or video gallery
ParentComponent.vue
<template>
<div>
<button class="btn-secondary" #click="showGallery('IMAGE')">
{{ $t('showImages') }}
</button>
<button class="btn-secondary" #click="showGallery('VIDEO')">
{{ $t('playVideo') }}
</button>
<LightGallery
v-if="galleryVisible" :gallery-items="galleryItems"
:gallery-type="galleryType" :user-subscription="userSubscription">
</LightGallery>
</div>
<template>
<script>
import LightGallery from "./subComponents/LightGallery.vue";
export default {
name: "Location",
components: {
LightGallery: LightGallery,
},
data() {
return {
// image / video slide show data
locationImages: [],
locationVideo: [],
galleryItems: {},
galleryVisible: false,
galleryType: 'IMAGE',
};
},
methods: {
showGallery(type) {
this.galleryItems = {};
this.galleryVisible = true;
this.galleryType = type;
if (type === 'IMAGE') {
this.galleryItems = this.locationImages;
} else if (type === 'VIDEO') {
this.galleryItems = this.locationVideo
}
},
hideGallery() {
this.galleryItems = {};
this.galleryVisible = false;
},
};
</script>
And this is my child (lightgallery) component
<template>
<div id="lightgallery">
</div>
</template>
<script>
// Light gallery
import 'lightgallery.js'
import 'lightgallery.js/dist/css/lightgallery.css'
import 'lg-zoom.js'
import 'lg-autoplay.js'
import 'lg-fullscreen.js'
import 'lg-hash.js'
import 'lg-pager.js'
import 'lg-share.js'
import 'lg-thumbnail.js'
import 'lg-video.js'
// Light gallery
export default {
name: "LightGallery",
props: {
galleryItems: {
type: Array,
required: true
},
galleryType: {
type: String,
required: true
},
userSubscription: {
type: Object,
required: false,
default: {
active: false
}
}
},
methods: {
openGallery() {
// prepare images / video to display them in lightGallery
let dynamicElements = [];
if (this.galleryType === 'IMAGE') {
this.galleryItems.forEach(function (value) {
dynamicElements.push({
src: '/' + value.hd_path,
thumb: '/' + value.thumb_path,
});
});
}
if (this.galleryType === 'VIDEO') {
this.galleryItems.forEach(function (value) {
dynamicElements.push({
src: 'https://vimeo.com/' + value.vimeo_id,
});
});
}
let lg = document.getElementById('lightgallery');
window.lightGallery(lg, {
mode: 'lg-slide',
download: false,
thumbnail: true,
dynamic: true,
dynamicEl: dynamicElements,
autoplayFirstVideo: true,
loadVimeoThumbnail: false,
});
lg.addEventListener('onCloseAfter', function (event, index, fromTouch, fromThumb) {
lg.data('lightGallery').destroy(true);
}, false);
window.lightGallery(lg);
},
},
mounted() {
this.openGallery();
}
};
</script>
The problem!
On first "refresh" page reload if I click on button Show image or show video, gallery opens and it works well. When I close gallery I see error in developer tools (maybe complletly unrelated, with my problem)
Each next click on buttons Show image or show video does nothing. So how can I properly destroy Lightgallery so I will be able to reopen it? If you need any additional informations, please let me know and I will provide. Thank you!

Try adding this line to your showGallery method.
showGallery(type) {
this.galleryVisible = false // <<--- this line
this.galleryItems = {};
this.galleryVisible = true;
this.galleryType = type;
if (type === 'IMAGE') {
this.galleryItems = this.locationImages;
} else if (type === 'VIDEO') {
this.galleryItems = this.locationVideo
}
},
Light Gallery only seems to work once. But the above line of code will re-create the Light Gallery Component and you'll have a fresh instance of Light Gallery component (exactly what you have on a page load) each time you click on one of the buttons that would initiate the Light Gallery.

Related

VueJS - Run Code after event is successfully emitted

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

Stripe js: don't let empty form be sent

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>

How to disable Vue Component if Ajax call will fail

I'm working on a Vue project which has a component for loading content into a modal via an ajax call:
<load-content target="foo"></load-content>
<load-content target="bar"></load-content>
<load-content target="oof"></load-content>
<load-content target="rab"></load-content>
Here's an example template:
<template>
<span class="load-content-wrapper" v-on:click="load">
Click
</span>
</template>
<script>
export default {
name: 'load content',
props: {
target: {
type: String,
required: true
}
},
methods: {
load() {
$('#load-content-modal').modal('show');
this.$store.dispatch('loadContent', this.target);
},
}
};
</script>
Which would trigger this example action:
const actions = {
loadContent ({ commit }, target) {
$.ajax({
url: '/api/fetch-content/' + target,
}).then((data) => {
// Load Modal Window
});
},
};
This all works well, except we cannot guarantee that the Ajax call will always return content. Depending on the target it could return 404.
Ideally I want to automatically disable individual load-content components if '/api/fetch-content/' + target isn't available to prevent users from trying to select unavailable content.
What is the correct/ most efficient way to do this?
You should make your "target" field not required and instead add a default value empty string.
And add an "if" condition to your load method. If "target" is empty, it will not proceed.
export default {
name: 'load content',
props: {
target: {
type: String,
default: ''
}
},
methods: {
load() {
if (!this.target) return;
$('#load-content-modal').modal('show');
this.$store.dispatch('loadContent', this.target);
},
}
};
Create a store variable loading and mutate it in your actions as follows:
loading: false
const actions = {
loadContent ({ commit }, target) {
$.ajax({
url: '/api/fetch-content/' + target,
}).then((data) => {
// Load Modal Window
commit(setLoading)
});
},
};
Then in muatations ->
setLoading (state, loading) {
state.loading = true
}
Now in your vue file use this store variable and check if it is true then load the component.You may check this created or mounted events of the component.
Option 1
Preemptively load the content, and disable the ones that return an error.
This is what the parent component will look like
<template>
<load-content
v-for="(target, index) in loadedTargets"
:key="index"
target="target"
/>
</template>
<script>
export default {
name: 'load content parent',
data: function() {
return {
targets: [
{link: 'foo', data: null, loaded: false, error: null},
{link: 'bar', data: null, loaded: false, error: null},
{link: 'oof', data: null, loaded: false, error: null},
{link: 'rab', data: null, loaded: false, error: null},
]
}
},
computed: {
loadedTargets() {
return this.targets.filter(t => t.loaded)
}
},
methods: {
load(target) {
const self = this;
$.ajax({
url: '/api/fetch-content/' + target.link,
}).then((data) => {
self.targets[indexOf(target)].data = data
self.targets[indexOf(target)].loaded = true
}).catch((error) => {
self.targets[indexOf(target)].error = error
});
},
},
mounted() {
this.targets.forEach(target => this.load(target))
}
};
</script>
Option 2
Preemptive loading is expensive (and since I don't know how many targets you might have), you could also show success/error in the modal. Proper UX would dictate that an explicit action by the user should lead to a result (i.e. if the user clicks a link, he should either see data in the modal, or an error)
This is what your action will look like:
const actions = {
loadContent ({ commit }, target) {
$.ajax({
url: '/api/fetch-content/' + target,
}).then((data) => {
// Load Modal Window
}).catch((error) => {
// Load Modal Window, and show the error
});
},
};

How to share a method between two components in Vue.js?

I have an Ag-Grid that has certain action buttons and dynamic data getting filled from a MongoDB database. I have a method on my MasterData.Vue file that refreshes the Grid. Each action button inside my grid's record perform update/delete operations. When I click on those buttons I have designed a customized pop up modal component in another Modal.Vue file. I want to call that RefreshGrid() method in Modal.Vue. I tried using props to share the data but same thing doesn't work on method.
MasterData.Vue Script
<script>
import { AgGridVue } from 'ag-grid-vue';
import { mapGetters } from 'vuex';
import gridEditButtons from '#/components/GridEditButton';
import MasterModal from '#/components/MasterModal';
export default {
name: 'masterData',
data () {
return {
addBtnClick: false,
delBtnClick: false,
editVisible: false,
selected: 'Business Area',
dropdown_tables: [
'Business Area',
'Council',
'Sub Area',
'Type',
'Work Flow Stage'
],
gridOptions: {
domLayout: 'autoHeight',
enableColumnResize: true,
rowDragManaged: true,
animateRows: true,
context: {
vm: null
}
}
};
},
components: {
'ty-master-modal': MasterModal,
'ag-grid-vue': AgGridVue,
gridEditButtons
},
methods: {
// Filter Grid Contents based on Dropdown selection
RefreshGrid: function () {
let cName;
if (this.selected === 'Business Area') {
cName = 'businessarea';
} else if (this.selected === 'Council') {
cName = 'council';
} else if (this.selected === 'Type') {
cName = 'typemaster';
} else if (this.selected === 'Work Flow Stage') {
cName = 'workflowstage';
}
let obj = {
vm: this,
collectionName: cName,
action: 'masterData/setMasterData',
mutation: 'setMasterData'
};
this.$store.dispatch(obj.action, obj);
}
};
</script>
Modal.Vue Script
<script>
import {mapGetters} from 'vuex';
export default {
name: 'MasterModal',
props: {
readOnly: Boolean,
entryData: Object,
addBtnClick: Boolean,
delBtnClick: Boolean,
editVisible: Boolean,
selectedTable: String
},
data () {
return {
fieldAlert: false,
isReadOnly: false,
dialog: false,
dialogDelete: false,
valid: false,
visible: false,
disable: false
};
},
computed: {
...mapGetters('masterData', {
entryState: 'entryState',
// entryData: 'entryData',
columns: 'columns',
selectedRowId: 'selectedRowId'
})
},
watch: {
addBtnClick: function (newValue, oldValue) {
this.setDialog(!this.dialog);
},
editVisible: function (newValue, oldValue) {
this.setVisible(!this.visible);
},
delBtnClick: function (newValue, oldValue) {
this.setDialogDelete(!this.dialogDelete);
}
},
methods: {
setDialog (bValue) {
this.dialog = bValue;
},
setDialogDelete (bValue) {
this.dialogDelete = bValue;
},
}
};
</script>
there are a couple of ways to achieve this.
One is to use the emit
in the MasterModal.vue component run this.$emit('refreshGrid') in the parent MasterData.Vue component use <ty-master-modal #refreshGrid="RefreshGrid" ...>
if you have a direct parent-child relationship, this is likely the best option
Another way is just to pass a function as a prop to the child component.
<ty-master-modal :onRefreshGrid="RefreshGrid" ...>
and add a prop onRefreshGrid to MasterModal.vue, then you can invoke the function.
Another way, using vuex, is to add a watch to MasterData.Vue and watch a variable in the vuex store ie. actionInvoker. when actionInvoker changes, the action executes. To change the value, set it to 0 and increment or toggle between, or set to random value. The advantage is that you can call this from anywhere.
The problem with this (and the previous) solution is that you have functionality tied to a view/component that shouldn't be there. I would recommend a third solution, which is to push the functionality into a vuex action, and then you can call it from anywhere. This would require though that you store the selected variable in vuex too, and if you want to have multiple instances of Modal and Master components, a singular store will prohibit that (unless you add support for multiple instances).

Extjs not able to upload file second time when it was already uploaded

I have a strange behavior of extjs file upload.
The file upload defined as:
items: [{
xtype: 'filefield',
itemId: 'uploadandsign',
buttonText: NG.getLabel('browse'),
buttonOnly: true,
hideLabel: true,
width: 100
}]
If the file uploading is success I show successful label on the screen with remove "X" button:
onOpenFileBrowserChange: function (filefield, newValue, oldValue, eOpts) {
var me = this,
form = filefield.up('form').getForm(),
infoBox = invoiceorigin.down('#fileuploadinfoplaceholder'),
fileDescription,
secondfilefield,
customerFileName = newValue.replace(/^.*[\\\/]/, ''),
draft = me.getDraft(),
isSigned = true,
files = draft.files();
if (filefield.itemId === 'uploadandsign') {
isSigned = false;
secondfilefield = invoiceorigin.down('#uploadnosign');
fileDescription = 'File system, Unsigned';
}
secondfilefield.disable();
if (form.isValid()) {
form.submit({
url: NG.getLatestApiPath('WebInvoice', 'UploadInvoiceFile'),
waitMsg: NG.getLabel('webinvoiceInvoiceOriginUploadingFile'),
success: function (fp, o) {
if (o.result.success) {
var file = o.result.file;
files.add({
fileName: file.fileName,
createDate: file.createDate,
isAttachment: false,
isSigned: isSigned,
fileOrigin: fileDescription,
customerFileName: customerFileName,
invoiceFileOrigin: 'Local'
});
filefield.disable();
infoBox.removeAll();
infoBox.add(Ext.create('NG.view.webinvoice.InformationBox', {
data: {
closable: true,
icon: true,
iconCls: 'n-pdf-icon',
content: '<div class="n-text-overflow" style="width:145px;">' + fileDescription + '<br/>' + customerFileName + '</div>'
}
}));
}
else {
}
},
failure: function (form, action) {
}
});
}
return false;
},
Then if I remove the file from #infobox, the reset() function called:
onRemoveFileClick: function (view) {
var me = this,
invoiceorigin = view.up('invoiceorigin'),
uploadNoSignBtn = invoiceorigin.down('#uploadnosign'),
uploadAndSignBtn = invoiceorigin.down('#uploadandsign'),
infoBox = invoiceorigin.down('#fileuploadinfoplaceholder'),
draft = me.getDraft(),
files = draft.files(),
pagemanager = view.up('webinvoicepagemanager'),
invoiceFilePlace = files.findExact('isAttachment', false);
me.deleteFileConfirmReject(
NG.getLabel('webinvoiceInvoiceOriginDeleteInvoiceFileTitle'),
NG.getLabel('webinvoiceInvoiceOriginDeleteInvoiceFileMsg'),
function (buttonId, text, opt) {
if (buttonId == 'yes') {
infoBox.removeAll();
if (invoiceFilePlace > -1) {
files.removeAt(invoiceFilePlace);
}
me.fillInvoiceOriginStep(pagemanager);
uploadNoSignBtn.reset();
uploadAndSignBtn.reset();
uploadNoSignBtn.enable();
uploadAndSignBtn.enable();
}
});
}
After this action if I will choose the same file.... nothing happens with page... no any change event fired on the page.. However if I choose different file the behavior is OK.
In the ExtJS documentation said that the reset() function have to clear previous files uploads... however it does not helps.
Is any body met such file upload ExtJS behaivour and could help with this issue?
Thanks a lot.
What I tried and worked quite good was to get the file from the form with a typical JS document.getElementsByName('[name you gave]'); and it got perfectly the file uploaded without mattering in wich execution you are in.
Hope it helps.

Categories