Element UI draggable dialog - vue.js

I use inside my Laravel application vue 2.6.14 and element-ui 2.15.6
I have a dialog which I would like to be draggable.
In element-plus I found that the draggable option exists for dialog. Although it is not mentioned in the element-ui documentation, I tried to add it. To my surprise, the editor completed this attribute as it is actually implemented.
However, the behavior is really strange as you can see:
var Main = {
data() {
return {
isNormalDialogVisible: false,
isDraggableDialogVisible: false,
}
},
methods: {
openNormalDialog() {
console.log("1");
this.isNormalDialogVisible = true;
},
openDraggableDialog() {
console.log("2");
this.isDraggableDialogVisible = true;
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
.green {
background-color: #67c23a !important;
}
.yellow {
background-color: #e6a23c !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.15.6/index.min.js" integrity="sha512-YXb3YAMf5gMN1byYIGwVydJojIIYxbQDvTu1IJivRe6aXGB2yGRtuFAofi9esNMfn2AUKOT2hFKhrhX8QddyPA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.15.6/theme-chalk/index.min.css" integrity="sha512-oKuj7gKXGxOZuCSvOPZE1seWosmP4iy608AyAH4hPnGD6tPX4325S+wo6MJQz+L3Hq4tK3e0RMfu+TDFUZFrgA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<div id="app">
<template>
<el-button #click="openNormalDialog()" type="success" plain>
Open normal dialog
</el-button>
<el-dialog :visible.sync="isNormalDialogVisible"
title="Normal dialog"
custom-class="green">
</el-dialog>
<el-button #click="openDraggableDialog()" type="warning" plain>
Open draggable dialog
</el-button>
<el-dialog :visible.sync="isDraggableDialogVisible"
title="Draggable dialog"
custom-class="yellow"
draggable>
Click mouse button and drag
</el-dialog>
</template>
</div>
Any idea?

Related

vuetify v-alert incorrectly dismissing sibling when transition is applied

I am using vuetify v-alert with v-for=alert in alerts looping through alerts array, when an alert is dismissed #input event is fired and I'm removing it from alerts array.
The problem I am facing, that when the element is clicked, transition is applied, and the sibling alert is now shown at the position where the one which was dismissed, seems like the click event is fired for the sibling element as well and the sibling v-alert is isVisible == false but the #input event is not fired.
If I remove transition="scroll-y-reverse-transition" from the v-alert it works properly.
Why is this happening?
const alertsComponent = {
name: "MyAlertsComponent",
template: "#alerts",
data() {
return {
alerts: []
};
},
created() {
this.$root.$off("alert");
this.$root.$on("alert", this.addAlert);
},
methods: {
closeAlert(idx, alert) {
console.log(`deleting alert idx: ${idx} - ${alert}`);
this.$delete(this.alerts, idx);
},
addAlert(alert, ...args) {
alert.type = alert.type || "error";
this.alerts.unshift(alert);
}
}
};
const app = new Vue({
el: "#app",
vuetify: new Vuetify(),
components: {
alertsComponent
},
mounted() {
[...Array(8).keys()].forEach((e) => {
this.fireAlert(this.counter++);
});
},
methods: {
fireAlert(val = this.counter++) {
const alert = this.generateAlert(val);
this.$root.$emit("alert", alert);
},
generateAlert(val, type = "error") {
return {
val,
type
};
}
},
data() {
return {
counter: 1
};
}
});
.alert-section {
max-height: 400px;
padding: 5px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<div id="app">
<v-app>
<v-main>
<v-container>
<header>VueTify!</header>
<hr>
<v-row>
<v-col>
<v-btn #click="fireAlert()">Add Alert</v-btn>
</v-col>
</v-row>
<alerts-component>Hi</alerts-component>
</v-container>
</v-main>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<template id="alerts">
<div>
<div class="alert-section overflow-y-auto">
<v-alert v-for="(alert, index) in alerts" :key="index" dense dismissible elevation="5" text :type="alert.type" #input="closeAlert(index, alert)" #addAlert="addAlert"
transition="scroll-y-reverse-transition"
>
{{ alert.val }}
</v-alert>
</div>
<hr>
<pre class="code">{{ JSON.stringify(alerts, null, 2) }}
</pre>
</div>
</template>
Looks like the problem is using the index as key.
If I change :key="alert.val" it works fine.

How to make the button animates in vue.js

How to animate(toggle) the button it self when click?
I tried as below
<div id="app">
<button class="btn" #click="show = !show">
click
</button>
</div>
<script>
var app = new Vue({
el: '#app',
data: function() {
return {
show: true
}
}
});
</script>
<style>
.button { position:fixed; top:100px; left:100px; }
.button.active { left:0; }
</style>
I expect the output that when click the button, button move to left:0 position. And another click, it move to left 100.
Use class binding to bind the active class when show is true. You should also use the button class instead of btn as that's what you have in your CSS.
var app = new Vue({
el: '#app',
data: {
show: true
}
})
.button {
position: fixed;
top: 100px;
left: 100px;
transition: left 0.2s; /* added this for fun */
}
.button.active {
left: 0;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<button class="button" :class="{active: show}" #click="show = !show">
click
</button>
</div>

How to use x-template to separate out template from Vue Component

Tried to separate out template from Vue Component as below but it does not work.
Referencing only vue.js file and not browsify.
Vue.component('my-checkbox', {
template: '#checkbox-template',
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
});
<script type="text/x-template" id="checkbox-template">
<div class="checkbox-wrapper" #click="check">
<div :class="{ checkbox: true, checked: checked }"></div>
<div class="title"></div>
</div>
</script>
Or any alternate way to separate out template from vue component.
You define X-Templates in your HTML file. See below for a brief demo
// this is the JS file, eg app.js
Vue.component('my-checkbox', {
template: '#checkbox-template',
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
});
new Vue({el:'#app'})
/* CSS file */
.checkbox-wrapper {
border: 1px solid;
display: flex;
}
.checkbox {
width: 50px;
height: 50px;
background: red;
}
.checkbox.checked {
background: green;
}
<!-- HTML file -->
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.min.js"></script>
<script type="text/x-template" id="checkbox-template">
<div class="checkbox-wrapper" #click="check">
<div :class="{ checkbox: true, checked: checked }"></div>
<div class="title">{{ title }}</div>
</div>
</script>
<div id="app"> <!-- the root Vue element -->
<my-checkbox></my-checkbox> <!-- your component -->
</div>
Sorry for my bad english it's not my main language!
Try it!
You need generate two file in same directory:
path/to/checkboxComponent.vue
path/to/checkboxComponent.html
In checkboxComponent.vue file
<script>
// Add imports here eg:
// import Something from 'something';
export default {
template: require('./checkboxComponent.html'),
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
}
</script>
In checkboxComponent.html file
<template>
<div class="checkbox-wrapper" #click="check">
<div :class="{ checkbox: true, checked: checked }"></div>
<div class="title"></div>
</div>
</template>
Now you need to declare this Component in same file you declare Vue app, as the following:
Vue.component('my-checkbox', require('path/to/checkboxComponent.vue').default);
In my case
I have three files with these directories structure:
js/app.js
js/components/checkboxComponent.vue
js/components/checkboxComponent.html
In app.js, i'm declare the Vue app, so the require method path need to start with a dot, like this:
Vue.component('my-checkbox', require('./components/checkboxComponent.vue').default);

Can't show the all the map on a DIV with VueJs and leaflet until resizing the screen

I am using VueJS and Leaflet to show the map with special localisation. I have added the css leaflet on index.html as told in the documentation.
link rel="stylesheet"
href="https://unpkg.com/leaflet#1.2.0/dist/leaflet.css">
But I have just a part of the map.
I have to change the size of the screen to have all the map with the marker.
This is the vue where I implement the map (iMap.vue)
<template>
<div id="professionnel">
<b-row>
<b-tabs>
<b-tab title="A" >
<div>
<b-col class="col-12">
<div>Où nous joindre</div>
</b-col>
</div>
</b-tab>
<b-tab title="B">
<div class="tab-content-active" >
<b-col class="col-6">
<div>heure</div>
</b-col>
<b-col class="col-6 data_map">
<iMap1></iMap>
</b-col>
</div>
</b-tab>
</tabs>
</b-row>
</div>
</template>
<script>
import iMap1 from './iMap1'
export default {
name: 'professionnel',
components: {
iMap1
},
data() {
return {}
}
</script>
And this is the vue of the Map (iMap.vue)
<template>
<div id="imap1" class="map" >
</div>
</template>
<script>
import leaflet from 'leaflet'
export default {
name: 'imap1',
components: {
leaflet
},
data() {
return {
professionnel: {},
map1: null,
tileLayer: null,
}
},
methods: {
initMap() {
this.map1 = L.map('imap1').setView([47.413220, -1.219482], 12)
this.tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
//'https://cartodb-basemaps-{s}.global.ssl.fastly.net/rastertiles/voyager/{z}/{x}/{y}.png',
{
maxZoom: 18,
center: [47.413220, -1.219482],
attribution: '© OpenStreetMap, © CARTO',
}).addTo(this.map1)
L.marker([47.413220, -1.219482]).addTo(this.map1).bindPopup('name')
.openPopup()
this.map1.invalidateSize()
})
},
},
created () {
this.initMap()
}
}
</script>
Use the mounted lifecycle hook instead of the created one.
created is typically to subscribe to some data / start some async processes, whereas mounted is rather when the DOM of your component is ready (but not necessarily insterted in the page IIRC).
Then, as explained in Data-toggle tab does not download Leaflet map, you have to use invalidateSize after the Tab that contains your Map container is opened, i.e. you have to listen to an event that signals that your user has opened the Tab.
In the case of Bootstrap-Vue, you have the <b-tabs>'s "input" event, but which signals only when the user has clicked on the Tab. But the latter is still not opened. Therefore you have to give it a short delay (typically with setTimeout) before calling invalidateSize:
Vue.use(bootstrapVue);
Vue.component('imap', {
template: '#imap',
methods: {
resizeMap() {
if (this.map1) {
this.map1.invalidateSize();
// Workaround to re-open popups at their correct position.
this.map1.eachLayer((layer) => {
if (layer instanceof L.Marker) {
layer.openPopup();
}
});
}
},
initMap() {
this.map1 = L.map('imap1').setView([47.413220, -1.219482], 12)
this.tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
center: [47.413220, -1.219482],
attribution: '© OpenStreetMap',
}).addTo(this.map1)
L.marker([47.413220, -1.219482]).addTo(this.map1).bindPopup('name')
.openPopup() // Opening the popup while the map is incorrectly sized seems to place it at an incorrect position.
},
},
mounted() {
this.initMap()
},
});
new Vue({
el: '#app',
methods: {
checkMap(tab_index) {
if (tab_index === 1) {
// Unfortunately the "input" event occurs when user clicks
// on the tab, but the latter is still not opened yet.
// Therefore we have to wait a short delay to allow the
// the tab to appear and the #imap1 element to have its final size.
setTimeout(() => {
this.$refs.mapComponent.resizeMap();
}, 0); // 0ms seems enough to execute resize after tab opens.
}
}
},
});
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.1/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.3.1/dist/leaflet-src.js" integrity="sha512-IkGU/uDhB9u9F8k+2OsA6XXoowIhOuQL1NTgNZHY1nkURnqEGlDZq3GsfmdJdKFe1k1zOc6YU2K7qY+hF9AodA==" crossorigin=""></script>
<script src="https://unpkg.com/vue#2"></script>
<link rel="stylesheet" href="https://unpkg.com/bootstrap#4/dist/css/bootstrap.css" />
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.0.0-rc.11/dist/bootstrap-vue.css" />
<script src="https://unpkg.com/bootstrap-vue#2.0.0-rc.11/dist/bootstrap-vue.js"></script>
<div id="app">
<!-- https://bootstrap-vue.js.org/docs/components/tabs -->
<b-tabs #input="checkMap">
<b-tab title="First Tab" active>
<br>I'm the first fading tab
</b-tab>
<b-tab title="Second Tab with Map">
<imap ref="mapComponent"></imap>
</b-tab>
</b-tabs>
</div>
<template id="imap">
<div id="imap1" style="height: 130px;"></div>
</template>

Vuejs: Using keep-alive (Or something similar) on a slot

I've got a combination of tree hierarchy and tabs in vue. So far I've gotten the unfolding more or less working.
I need to remove things from the dom entirely when they're closed, because the data I'm talking about is large enough to bring a browser to its knees if it's all just left in the dom as display:none.
Take a look at this example:
Vue.component('tabs', {
template: '#tabs',
data(){
return {
tabs: [],
expanded:true,
defaultExpanded:true,
activeTab: null,
hasChildren:false,
};
},
methods: {
toggle() {
this.expanded = !this.expanded;
},
activate(tab) {
if (this.activeTab) {
this.activeTab.active = false;
}
tab.active = true;
this.activeTab = tab;
},
},
mounted(){
for (i = 0; i < this.$slots.default.length; i++) {
let t = this.$slots.default[i];
if (t.componentOptions && t.componentOptions.tag == 'tab') {
this.tabs.push(t.componentInstance);
}
}
if (this.tabs.length) {
this.activeTab = this.tabs[0];
this.activeTab.active = true;
}
this.expanded = this.defaultExpanded;
},
});
Vue.component('tab', {
template: '#tab',
data() {
return {
active: false,
};
},
props: ['label'],
});
app = new Vue({
'el': '#inst',
});
<!-- templates -->
<script type="text/x-template" id="tabs">
<div #click.stop="toggle">
<h1><slot name="h" /></h1>
<div v-show="expanded" class="children">
<ul><li v-for="tab in tabs" #click.stop="activate(tab)">{{tab.label}}</li></ul>
<div style="border:1px solid #F00"><slot /></div>
</div>
</script>
<script type="text/x-template" id="tab">
<strong v-show="active"><slot /></strong>
</script>
<!-- data -->
<tabs id="inst">
<div slot="h">Woot</div>
<tab label="label">
<tabs>
<div slot="h">Weet</div>
<tab label="sub">Weetley</tab>
</tabs>
</tab>
<tab label="label2">Woot3</tab>
</tabs>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
This works fine, but if I change the v-show to v-if for performance, it loses state, tab buttons stop showing - basically lots of stuff breaks.
The problem is that as soon as I add v-if to the tab template's slot the entire component is removed when it's closed. This means the parent component's tabs list is a completely different bunch of objects than the ones that show up when it's opened a second time.
This means I can't click on a label to open a tab, since the tabs will be different instances by the time I get to them, and all the tabs will default to closed every time I close and open the parent.
What I really need is something like <keep-alive> - where I could tell vue to keep the components alive in memory without rendering them to the dom. But when I add that the entire thing stops working. It seems like it really doesn't work on slots, only on individual components.
So. tl;dr: How do I maintain the state of mixed trees and tabs while using v-if to keep the dom light?
Building on Bert Evans' codepen, I created a component that is just a slot. I made a keep-alive-wrapped dynamic component that is the slot-component when active and a blank component when not. Now there is no v-if and state is preserved in the children when you close and re-open the parent.
console.clear();
Vue.component('keepableSlot', {
template: '#keepable-slot'
});
Vue.component('tabs', {
template: '#tabs',
data() {
return {
tabs: [],
expanded: true,
activeTab: null,
};
},
methods: {
addTab(tab) {
this.tabs.push(tab)
},
toggle() {
this.expanded = !this.expanded;
},
activate(tab) {
if (this.activeTab) {
this.activeTab.active = false;
}
tab.active = true;
this.activeTab = tab;
},
},
watch: {
expanded(newValue) {
console.log(this.$el, "expanded=", newValue);
}
}
});
Vue.component('tab', {
props: ["label"],
template: '#tab',
data() {
return {
active: false
}
},
created() {
this.$parent.$parent.addTab(this)
}
});
app = new Vue({
'el': '#inst',
});
.clickable-tab {
background-color: cyan;
border-radius: 5px;
margin: 2px 0;
padding: 5px;
}
.toggler {
background-color: lightgray;
border-radius: 5px;
margin: 2px 0;
padding: 5px;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<script type="text/x-template" id="tabs">
<div>
<h1 class="toggler" #click.stop="toggle">
<slot name="h"></slot>
(expanded={{expanded}})
</h1>
<keep-alive>
<component :is="expanded && 'keepableSlot'">
<div class="children">
<ul>
<li class="clickable-tab" v-for="tab in tabs" #click.stop="activate(tab)">{{tab.label}}</li>
</ul>
<div>
<slot></slot>
</div>
</div>
</component>
</keep-alive>
</div>
</script>
<script type="text/x-template" id="keepable-slot">
<div>
<slot></slot>
</div>
</script>
<script type="text/x-template" id="tab">
<strong>
<component :is="active && 'keepableSlot'"><slot></slot></component>
</div>
</script>
<!-- data -->
<tabs id="inst">
<div slot="h">Woot</div>
<tab label="label">
<tabs>
<div slot="h">Weet</div>
<tab label="sub">Weetley</tab>
</tabs>
</tab>
<tab label="label2">Woot3</tab>
</tabs>