Vue.js mounted method called twice - javascript

The method this.fillForm() of my Vue component C (EditComment) is called twice, but I'm having trouble understanding why. I tried using uuid, but don't know how it helps knowing that beforeCreate is called twice.
There are 3 components. Here are the relevant parts:
Component A:
showCommentDialog: function(recordNumber) {
this.$modal.show(
ShowComment,
{
commentRecId: recordNumber
},
{
draggable: true,
width: 400,
height: 250
},
{
closed: function(event) {}
}
);
Component B:
<EditComment v-bind:comment-rec-id="commentRecId" v-if="showEdit"></EditComment>
</div>
</template>
<script>
import * as $ from "jquery";
import EditComment from "./EditComment.vue";
export default {
props: ["commentRecId"],
data: function() {
with this function
editItem: function(){
this.showEdit = true;
console.log("editItem function() called!");
var playerID = this.$store.state.selectedPlayer.ID;
this.$modal.show(
EditComment,
{
text: playerID
},
{
draggable: true,
width: 400,
height: 400
})
}
Component C:
<script>
import * as $ from "jquery";
import DatePicker from "vue2-datepicker";
let uuid = 0;
export default {
props: ["text", "commentRecId"],
beforeCreate() {
this.uuid = uuid.toString();
uuid += 1;
console.log("beforeCreate() uuid: " + this.uuid);
},
components: { DatePicker },
data: function() {
return {
commentData: {
comment: "",
customDate: ""
},
selectedCategory: "",
lang: {
default: "en"
},
}
},
mounted: function() {
// console.log("this._uid: " + this._uid);
this.fillForm();
},
methods: {
fillForm: function(){
Any help is appreciated.

If I understand correctly your problem, you fired component C with this section of editItem method:
this.$modal.show(
EditComment,
{
text: playerID
},
{
draggable: true,
width: 400,
height: 400
})
if I'm right, you have a mistake in your method:
when you use v-if, vue fires your component and it resets all values which you passed it before, like props, data values (except your uuid because it's not a data property)
so in your method you fire your component twice with :
this.showEdit = true;
anyway...for solution, please try this way:
first, use "v-show" instead of "v-if"
then show your component by this.$modal.show()
I hope can help

Related

How to delay nth child item animation in Vue JS

I want delay animation for another item's directly by Vue Js, not css.
In this moment Vue wait until all items has rendered, and all appear at the same time.
How to do delay by change this.animated = true ?
Item.vue
<template>
<transition name="animation-fade">
<div v-if="this.animated">
{{this.itemProp.content}}
</div>
</transition>
</template>
<script>
export default {
name: 'Item',
props: {
itemProp: Object,
},
data: function() {
return {
animated: false,
loading: true,
};
},
methods: {
delayedShow: function(delay) {
setTimeout(this.toggleItem, delay)
},
},
toggleItem: function() {
this.animated = true;
},
mounted: function() {
this.delayedShow(500);
},
}
</script>
Items are defined in other component and every pass by prop to Item model.
Help me please.

CSS of component balise doesn't load inside spefic component

I've a problem to load the css of my bloc component.
The webpage component allow to create an iframe and set some content inside easily.
It load correctly the template and script tag but not the css (it doesn't load).
Sometime it works, most of the time, it didn't.
I was thinking that it was a problem with the loading of the component but no.
If I load the component before or after the render of my "webpage" component : it don't load.
I've try with the auto import to true and after to false, but it solve nothing.
I have 2 components : webpage and bloc.
bloc.vue
<template>
<div class="bloc">
<p>Le texte</p>
</div>
</template>
<style>
.bloc {
background-color: blue;
padding: 20px;
}
</style>
webpage.vue
<script>
import Vue from "vue";
export default {
props: {
css: {
type: String,
required: false,
},
},
data() {
return {
load: false,
};
},
render(h) {
return h("iframe", {
on: { load: this.renderChildren },
});
},
beforeUpdate() {
//freezing to prevent unnessessary Reactifiation of vNodes
this.iApp.children = Object.freeze(this.$slots.default);
},
mounted() {
if (!this.load) this.renderChildren();
},
methods: {
// https://forum.vuejs.org/t/render-inside-iframe/6419/12
renderChildren() {
this.load = true;
const children = this.$slots.default;
const head = this.$el.contentDocument.head;
const body = this.$el.contentDocument.body;
let style = this.$el.contentDocument.createElement("style");
style.textContent += this.$props.css;
head.appendChild(style);
const iApp = new Vue({
name: "iApp",
// freezing to prevent unnessessary Reactifiation of vNodes
data: { children: Object.freeze(children) },
render(h) {
return h("body", this.children);
},
});
this.iApp = iApp; // cache instance for later updates
this.iApp.$mount(body); // mount into iframe
},
},
};
</script>
app.vue
<template>
<Webpage>
<component :is="name"></component>
</Webpage>
</template>
<script>
import bloc from "#/components/Bloc";
import Webpage from "#/components/Webpage";
export default {
components: {
bloc,
Webpage,
},
computed: {
name() {
return "bloc";
},
},
};
</script>
Do you have an idea where this might come from ?
The codesanbox : https://codesandbox.io/s/error-style-component-import-1t1hs?file=/pages/index.vue
Thank you.
Probably Webpage component overrides it.
Try moving your style to index and <Webpage class="bloc">

Using VMenu from vuetify with render function (scoped slot)

I'm trying to use Vuetify's VMenu component and I would like that when a user clicks the button the VMenu shows up. As far as the docs goes it says we should add a scoped slot. Doing with a normal template it works but when I switch to a render function approach it never renders the button.
I have been following the Vue's docs and ended up with:
h(VMenu, { props: { value: isMenuOpen.value } }, [
h(
"template",
{
scopedSlots: {
activator: ({ on, attrs }) => {
debugger; // it never reaches this debugger
return h(VButton, { on, attrs }, 'click me');
}
},
},
[]
),
h(VList, [h(VListItem, [h(VListItemTitle, ["Logout"])])]),
]),
I have tried using a non-arrow function as well:
scopedSlots: { activator: function({ on, attrs }) { return h('div', 'click me'); } }
and return a simple h('div', 'click me') in both non-arrow function and arrow function and nothing seems to work.
How can I pass the scoped slot activator to VMenu component?
Scoped slots are passed through the scopedSlots property of createElement's 2nd argument in the form of { name: props => VNode | Array<VNode> }. In your case, scopedSlots should have two entries: one for activator, and another for default:
import { VMenu, VList, VListItem, VBtn } from 'vuetify/lib'
export default {
render(h) {
return h(VMenu, {
scopedSlots: {
activator: props => h(VBtn, props, 'Open'),
default: () => h(VList, [
h(VListItem, 'item 1'),
h(VListItem, 'item 2'),
h(VListItem, 'item 3'),
]),
},
})
}
}
which is equivalent to this template:
<template>
<v-menu>
<template v-slot:activator="{ on, attrs }">
<v-btn v-bind="attrs" v-on="on">Open</v-btn>
</template>
<v-list>
<v-list-item>item 1</v-list-item>
<v-list-item>item 2</v-list-item>
<v-list-item>item 3</v-list-item>
</v-list>
</v-menu>
</template>
demo
I wasn't able to fully understand the problem described in my question. This is an answer not to answer the fully original question but to guide future users that may come to this question.
Instead of using a scoped slot I have used the value prop in combination with attach prop. This solution in the end ended up working without no problem.
button(
{
attrs: { "data-account-setting": true },
props: { plain: true, rounded: true, icon: true },
on: { click: onOpenMenuClick },
},
[h(VIcon, ["mdi-account-outline"])]
),
h(
VMenu,
{
props: {
value: isMenuOpen.value,
// waiting on answer on SO
// #see https://stackoverflow.com/questions/67405594/using-vmenu-from-vuetify-with-render-function-scoped-slot
attach: "[data-account-setting]",
minWidth: "300px",
left: true,
offsetY: true,
closeOnContentClick: false,
rounded: true,
},
on: {
input: (value: boolean) => {
isMenuOpen.value = value;
},
},
},
[
h(VList, { props: { dense: true } }, [
h(VListItem, { props: { to: { name: "logout" } } }, [
h(VListItemTitle, { attrs: { 'data-cy-logout': true } }, ["Logout"]),
]),
]),
]
),

Form.io Custom Layout Component

I am using Form.io v3.27.1, and I am trying to create a custom layout component - specifically an accordion - and I'm using the concepts provided in the CheckMatrix component example for the most part.
I am able to make the accordion component show in the toolbox, and I'm able to drag it onto the form, configure it with a custom edit form etc. I can save it and it renders a Bootstrap themed accordion perfectly.
What it does NOT do however, is allow me to drag and drop other components into the content area similar to the behavior of other layout components (i.e. Tabs, Columns, Fieldset etc.).
I assume by skimming through the source code of the other layout controls that I need to extend NestedComponent in lieu of BaseComponent, but I've not yet been able to make that work.
I feel like I am overlooking something small. I just can't seem to figure out how to render a layout component that accepts other Form.io components as children.
Anyone have a working example or suggestions I can try to get this working? I appreciate your help in advance!
import BaseComponent from 'formiojs/components/base/Base';
import NestedComponent from 'formiojs/components/nested/NestedComponent';
import Components from 'formiojs/components/Components';
import * as editForm from './Accordian.form';
export default class AccordionComponent extends BaseComponent {
/**
* Define what the default JSON schema for this component is. We will derive from the BaseComponent
* schema and provide our overrides to that.
* #return {*}
*/
static schema() {
return BaseComponent.schema({
type: 'accordion',
label: 'Sections',
input: false,
key: 'accordion',
persistent: false,
components: [{
label: 'Section 1',
key: 'section1',
components: []
}]
});
}
/**
* Register this component to the Form Builder by providing the "builderInfo" object.
*/
static get builderInfo() {
return {
title: 'Accordion',
group: 'custom',
icon: 'fa fa-tasks',
weight: 70,
schema: AccordionComponent.schema()
};
}
/**
* Tell the renderer how to build this component using DOM manipulation.
*/
build() {
this.element = this.ce('div', {
class: `form-group formio-component formio-component-accordion ${this.className}`
}, [
this.ce('app-formio-accordian', {
components: JSON.stringify(this.component.components)
})
]);
}
elementInfo() {
return super.elementInfo();
}
getValue() {
return super.getValue();
}
setValue(value) {
super.setValue(value);
}
}
// Use the table component edit form.
AccordionComponent.editForm = editForm.default;
// Register the component to the Formio.Components registry.
Components.addComponent('accordion', AccordionComponent);
<div class="accordion" id="formioAccordionPreview" *ngIf="components">
<div class="card" *ngFor="let component of components; first as isFirst">
<div class="card-header" id="heading-{{component.key}}">
<h2 class="mb-0">
<button type="button" class="btn btn-link" data-toggle="collapse" data-target="#collapse-{{component.key}}">{{component.label}}</button>
</h2>
</div>
<div id="collapse-{{component.key}}" class="collapse" [class.show]="isFirst" aria-labelledby="heading-{{component.key}}" data-parent="#formioAccordionPreview">
<div class="card-body">
<p>I should be able to &apos;Drag and Drop a form component&apos; here.</p>
</div>
</div>
</div>
</div>
An Accordion is functionally identical to a tabs control, that is, headered content that facilitates switching and selection. The answer to constructing an accordion control was to extend the TabsComponent that's built into Form.io and override the createElement method that constructs the element via DOM manipulation. A couple of other overrides (schema and builderInfo) to provide metadata back to the FormBuilder and voila!
accordion.js
import TabsComponent from 'formiojs/components/tabs/Tabs';
import * as editForm from './Accordian.form';
export default class AccordionComponent extends TabsComponent {
/**
* Define what the default JSON schema for this component is. We will derive from the BaseComponent
* schema and provide our overrides to that.
* #return {*}
*/
static schema() {
return TabsComponent.schema({
type: 'accordion',
label: 'Sections',
input: false,
key: 'accordion',
persistent: false,
components: [{
label: 'Section 1',
key: 'section1',
type: 'tab',
components: []
}]
});
}
/**
* Register this component to the Form Builder by providing the "builderInfo" object.
*/
static get builderInfo() {
return {
title: 'Accordion',
group: 'custom',
icon: 'fa fa-tasks',
weight: 70,
schema: AccordionComponent.schema()
};
}
/**
* Tell the builder how to build this component using DOM manipulation.
*/
createElement() {
this.tabs = [];
this.tabLinks = [];
this.bodies = [];
this.accordion = this.ce('div', {
id: `accordion-${this.id}`
});
var _this = this;
this.component.components.forEach(function (tab, index) {
var isFirst = index === 0;
var tabLink = _this.ce('a', {
class: 'card-link',
data_toggle: 'collapse',
href: `#collapse-${tab.key}`
}, tab.label);
_this.addEventListener(tabLink, 'click', function (event) {
event.preventDefault();
_this.setTab(index);
});
var header = _this.ce('div', {
class: 'card-header'
}, [tabLink]);
var tabPanel = _this.ce('div', {
class: 'tab-pane',
role: 'tabpanel',
tabLink: tabLink
});
var tabContent = _this.ce('div', {
class: 'tab-content'
}, [tabPanel]);
var body = _this.ce('div', {
class: 'card-body',
id: tab.key
}, [tabContent]);
var content = _this.ce('div', {
id: `collapse-${tab.key}`,
class: 'collapse'.concat(isFirst ? ' show' : ''),
data_parent: `#accordion-${_this.id}`
}, [body]);
var card = _this.ce('div', {
class: 'card'
}, [header, body]);
_this.tabLinks.push(header);
_this.tabs.push(tabPanel);
_this.bodies.push(body);
_this.accordion.appendChild(card);
});
if (this.element) {
this.appendChild(this.element, [this.accordion]);
this.element.className = this.className;
return this.element;
}
this.element = this.ce('div', {
id: this.id,
class: this.className
}, [this.accordion]);
this.element.component = this;
return this.element;
}
setTab(index, state) {
super.setTab(index, state);
var _this = this;
if (this.bodies) {
this.bodies.forEach(function (body) {
body.style.display = 'none';
});
_this.bodies[index].style.display = 'block';
}
}
}
AccordionComponent.editForm = editForm.default;
The Accordion requires some different configuration in the edit form, so I also included a definition for the Display tab of the edit form:
Accordion.edit.display.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _default = [{
key: 'components',
type: 'datagrid',
input: true,
label: 'Sections',
weight: 50,
reorder: true,
components: [{
type: 'textfield',
input: true,
key: 'label',
label: 'Label'
}, {
type: 'textfield',
input: true,
key: 'key',
label: 'Key',
allowCalculateOverride: true,
calculateValue: {
_camelCase: [{
var: 'row.label'
}]
}
}]
}];
exports.default = _default;
And then the form definition override that references the custom Display Tab elements:
Accordion.form.js
"use strict";
require("core-js/modules/es.array.concat");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _default;
var _NestedComponent = _interopRequireDefault(require("../../../../../../../../../node_modules/formiojs/components/nested/NestedComponent.form"));
var _AccordianEdit = _interopRequireDefault(require("./editForm/Accordian.edit.display"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _default() {
for (var _len = arguments.length, extend = new Array(_len), _key = 0; _key < _len; _key++) {
extend[_key] = arguments[_key];
}
return _NestedComponent.default.apply(void 0, [[{
key: 'display',
components: _AccordianEdit.default
}]].concat(extend));
}
The file structure for the Accordion component looks like this:
Then I just need to register the component in my Angular Project:
app.component.ts
import { Component } from '#angular/core';
import { Formio } from 'formiojs';
import AccordionComponent from './modules/utility/form-shell/cap-forms/cap-form-designer/components/accordian/accordian';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'app';
constructor() {
Formio.registerComponent('accordion', AccordionComponent);
}
}

How to write a plugin that shows a modal popup using vue. Call should be made as a function()

I am trying to make a VueJS plugin that exports a global method, which when called, will popup a message with an input text field. Ideally, I want to be able to make the following call from any Vue component:
this.$disaplayMessageWithInput("Title","Body","Value");
And a popup should come on the screen.
I've tried building it but when the install() calls this.$ref., it isn't recognized:
DeleteConfirmation.vue
<template>
<b-modal size="lg" ref="deleteConfirmationModal" :title="this.title" header-bg-variant="danger" #ok="confirmDelete" #cancel="confirmCancel">
<p>
{{this.body}}
</p>
</b-modal>
</template>
<script>
export default {
data()
{
return {
title: null,
body: null,
valueCheck: null,
value: null
};
},
install(vue, options)
{
Vue.prototype.$deleteConfirmation = function(title, body, expectedValue)
{
this.title = title;
this.body = body;
this.valueCheck = expectedValue;
this.$refs.$deleteConfirmation.show()
}
},
}
</script>
app.js
import DeleteConfirmation from './components/global/DeleteConfirmation/DeleteConfirmation';
Vue.use(DeleteConfirmation);
The call I am trying to make is:
$vm0.$deleteConfirmation("title","body","val");
I get the below error at the run time:
app.js?id=c27b2799e01554aae7e1:33 Uncaught TypeError: Cannot read property 'show' of undefined
at Vue.$deleteConfirmation (app.js?id=c27b2799e01554aae7e1:33)
at <anonymous>:1:6
Vue.$deleteConfirmation # app.js?id=c27b2799e01554aae7e1:33
(anonymous) # VM1481:1
It looks like, this.$refs in DeleteConfirmation.vue is undefined.
Try to avoiding $ref with vue ( $ref is here for third party and some very special case )
$ref isn't reactive and is populate after the render ...
the best solution for me is using a event bus like this :
const EventBus = new Vue({
name: 'EventBus',
});
Vue.set(Vue.prototype, '$bus', EventBus);
And then use the event bus for calling function of your modal ...
(
this.$bus.on('event-name', callback) / this.$bus.off('event-name');
this.$bus.$emit('event-name', payload);
)
You can create a little wrapper around the bootstrap modal like mine
( exept a use the sweet-modal)
<template>
<div>
<sweet-modal
:ref="modalUid"
:title="title"
:width="width"
:class="klass"
class="modal-form"
#open="onModalOpen"
#close="onModalClose"
>
<slot />
</sweet-modal>
</div>
</template>
<script>
export default {
name: 'TModal',
props: {
eventId: {
type: String,
default: null,
},
title: {
type: String,
default: null,
},
width: {
type: String,
default: null,
},
klass: {
type: String,
default: '',
},
},
computed: {
modalUid() {
return `${this._uid}_modal`; // eslint-disable-line no-underscore-dangle
},
modalRef() {
return this.$refs[this.modalUid];
},
},
mounted() {
if (this.eventId !== null) {
this.$bus.$on([this.eventName('open'), this.eventName('close')], this.catchModalArguments);
this.$bus.$on(this.eventName('open'), this.modalRef ? this.modalRef.open : this._.noop);
this.$bus.$on(this.eventName('close'), this.modalRef ? this.modalRef.close : this._.noop);
}
},
beforeDestroy() {
if (this.eventId !== null) {
this.$off([this.eventName('open'), this.eventName('close')]);
}
},
methods: {
onModalOpen() {
this.$bus.$emit(this.eventName('opened'), ...this.modalRef.args);
},
onModalClose() {
if (this.modalRef.is_open) {
this.$bus.$emit(this.eventName('closed'), ...this.modalRef.args);
}
},
eventName(action) {
return `t-event.t-modal.${this.eventId}.${action}`;
},
catchModalArguments(...args) {
if (this.modalRef) {
this.modalRef.args = args || [];
}
},
},
};
</script>
<style lang="scss" scoped>
/deep/ .sweet-modal {
.sweet-title > h2 {
line-height: 64px !important;
margin: 0 !important;
}
}
</style>
AppModal.vue
<template>
<div class="modal-wrapper" v-if="visible">
<h2>{{ title }}</h2>
<p>{{ text }}</p>
<div class="modal-buttons">
<button class="modal-button" #click="hide">Close</button>
<button class="modal-button" #click="confirm">Confirm</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
title: '',
text: ''
}
},
methods: {
hide() {
this.visible = false;
},
}
}
</script>
Modal.js (plugin)
import AppModal from 'AppModal.vue'
const Modal = {
install(Vue, options) {
this.EventBus = new Vue()
Vue.component('app-modal', AppModal)
Vue.prototype.$modal = {
show(params) {
Modal.EventBus.$emit('show', params)
}
}
}
}
export default Modal
main.js
import Modal from 'plugin.js'
// ...
Vue.use(Modal)
App.vue
<template>
<div id="app">
// ...
<app-modal/>
</div>
</template>
This looks pretty complicated. Why don't you use a ready-to-use popup component like this one? https://www.npmjs.com/package/#soldeplata/popper-vue

Categories

Resources