Keep vue component events after moving - javascript

I have the following vue component
<template>
<div class="box"
:data-target="dropAreaClass"
:class="{ 'js-draggable': isDraggable }"
:id="id"
:draggable="isDraggable"
#dragstart="dragStart"
#dragend="dragEnd">
{{ id }}
</div>
</template>
<script>
export default {
name: 'ActionBox',
props: {
dropAreaClass: {
default: 'js-droppable--any',
type: String,
},
id: {
default: null,
type: String,
required: true,
},
isDraggable: {
default: true,
type: Boolean,
},
},
data: () => ({
dropAreas: null,
}),
mounted() {
this.dropAreas = document.querySelectorAll(`.${this.dropAreaClass}`);
},
methods: {
dragEnd(event) {
this.dropAreas.forEach(dropArea => {
dropArea.classList.remove('drop');
});
event.currentTarget.classList.remove('dragging');
},
dragStart(event) {
this.dropAreas.forEach(dropArea => {
dropArea.classList.add('drop');
});
event.currentTarget.classList.add('dragging');
event.dataTransfer.setData('text', event.currentTarget.id);
},
},
};
</script>
This is a simple div which I can drag a drop into multiple columns in the parent component - once it is dropped in one of the target columns, the following function is fired to move the component to the column it is dropped in:
drop(event) {
const droppedElement = document.getElementById(event.dataTransfer.getData('text'));
if (event.currentTarget.classList.contains(droppedElement.dataset.target)) {
event.currentTarget.prepend(droppedElement);
event.currentTarget.classList.remove('drop');
}
},
This all works fine, however, once it is dropped, I can no longer drag the component to another column as it seems to have lost all it's event bindings. Is there a way to keep the events after dropping?

Related

VueJS Duplicate components all updating at the same time

Might be a simple solution, but I'm currently not seeing it. I have an object that describes several configurations.
Object looks like this:
export const fieldSelectionDefault = {
cohort: {
currency_key: null,
salary_key: null,
timeframe_key: null
},
school: {
currency_key: null,
salary_key: null,
timeframe_key: null,
response_count_key: null,
},
}
export const cohortListFieldDefault = {
field_student: { ...fieldSelectionDefault },
field_alum_1: { ...fieldSelectionDefault },
field_alum_2: { ...fieldSelectionDefault },
field_alum_3: { ...fieldSelectionDefault },
}
Now, I have a parent component where I have a form. This form will list each field_* to have a <CohortFieldConfig /> component where we can input the values of the fieldSelectionDefault.
In the parent form, I add them like this:
<h5>Student</h5>
<CohortFieldConfig
:key="'settings.field_student'"
:disabled="settings.active_entities.student"
:selection-fields="settings.field_student"
#update-fields="(val) => test(val, 'stu')"
/>
<h5>Alumnus 1</h5>
<CohortFieldConfig
:key="'settings.field_alum_1'"
:disabled="settings.active_entities.alum_1"
:selection-fields="settings.field_alum_1"
#update-fields="(val) => test(val, 'alum')"
/>
CohortFieldConfig looks like this (example of one inputs, removed js imports):
<template>
<div>
<a-form-item label="Currency input">
<a-input
:disabled="!disabled"
placeholder="Select a currency form key"
v-model="objSelectionFields.cohort.currency_key"
/>
</a-form-item>
<FieldSelector
#select="val => (objSelectionFields.cohort.currency_key = val)"
:user="user"
:disabled="!disabled"
/>
</div>
</template>
<script>
export default {
name: 'CohortFieldConfig',
components: { FieldSelector },
props: {
selectionFields: {
type: [Object, null],
default: () => {
return { ...fieldSelectionDefault }
},
},
disabled: {
type: Boolean,
default: () => false,
},
},
data: function() {
return {
fieldSelectionDefault,
objSelectionFields: { ...this.selectionFields },
}
},
watch: {
objSelectionFields: {
handler(){
this.$emit('update-fields', this.objSelectionFields)
},
deep: true
}
},
methods: {
update() {
// not really used atm
this.$emit('update-fields', this.objSelectionFields)
},
},
}
</script>
When you type in the input, BOTH are updated at the same time. For student & alum_1.
The update-fields event is fired for both (same) components
Whats the reason? I've tried setting different key, doesn't work.
UPDATE
As pointed out in the comments, the issue was I was giving the same object. To correct this, I make a (deep) copy of the object as so:
export const cohortListFieldDefault = {
field_student: JSON.parse(JSON.stringify(fieldSelectionDefault)),
field_alum_1: JSON.parse(JSON.stringify(fieldSelectionDefault)),
field_alum_2: JSON.parse(JSON.stringify(fieldSelectionDefault)),
field_alum_3: JSON.parse(JSON.stringify(fieldSelectionDefault)),
}

Vue.js: Passing props to data to use in v-model

I've been trying to pass props to data like this, as I saw on another post:
Child Component:
props: {
idInput: { type: String, required: false },
nameInput: { type: String, required: false },
},
data() {
return {
id: this.idInput,
name: this.nameInput,
}
}
So I can use it here:
<t-input v-model="name" type="text" />
Parent Component:
data() { return { game: {} } },
beforeCreated() { this.game = { name: "myName", id: "myID" }
<ChildComponent :name-input="game.name" :id-input="game.id" />
The problem is that "name" appears to be undefined, while if I do the same but changing "name" to "nameInput" it works, but I get the Vue error telling me not to use props like that. Any ideas?
Here is a functional example I created to demonstrate this case:
const comp = Vue.component('comp', {
template: '#myComp',
props: {
idInput: { type: String, required: false },
nameInput: { type: String, required: false },
},
data() {
return {
id: this.idInput,
name: this.nameInput,
}
}
});
new Vue({
el: "#myApp",
data () {
return {
game: {}
}
},
created() {
this.game = { name: 'myName', id: 'myID' };
},
components: { comp }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="myApp">
<comp :name-input="game.name" :id-input="game.id" />
{{game}}
</div>
<template id="myComp">
<div>
{{idInput}}
<br>
<input v-model="name" type="text" />
</div>
</template>
EDIT:
After checking the code, I think the problem is that you're setting the game atrribute in the data of the parent component on beforeCreated. Set it on created instead.
EDIT
OP found another way to do it. Instead of passing props one by one, pass just 1 prop with the object and use its attributes on the v-model.

vuejs: vue-select component not updating values

I am trying to use the vue-select component for a dropdown list. So far I have written.
<template>
<div>
<v-select label="name" key="id" :v-model="selected" :reduce="data => data.id" :options="items" #input="update()" />
</div>
</template>
<script>
export default {
props: {
initial: {
type: [String, Number],
default: 0,
},
api_call: {
type: String,
required: true,
},
},
data(){
return {
value: this.initial,
items: [],
}
},
computed: {
selected: {
get() {
return this.value;
},
set(val) {
return this.value = val;
}
},
},
methods:{
update() {
console.log('selected', this.selected, this.value);
this.$emit('input', this.selected);
},
getData: function(){
axios.get('/api/' + this.api_call)
.then(function (response) {
this.items = response.data;
}.bind(this));
},
},
created(){
this.getData();
}
}
The dropdown list populates as intended and selecting an Item inserts it in the input filed. The two problems I have are
Neither the value nor the selected variables change when something is selected.
I am also passing in an initial value which I would like to be selected as the default in the list.
Remove the binding sign : from v-model directive
<v-select label="name" key="id" v-model="selected" :reduce="data => data.id" :options="items" #input="update()" />
and init your value like :
data(vm){//vm refers to this
return {
value: vm.initial,
items: [],
}
},
or :
data(){
return {
value: null,
items: [],
}
},
mounted(){
this.value=this.initial
}

CoreUI only one dropdown at a time

I want my sidebar dropdown to be available only one at a time, so when I click on another dropdown, the previous dropdown will be hidden again.
Below is the example of my current dropdown, which where you can open multiple dropdowns at a time. https://coreui.io/vue/demo/#/dashboard
<template>
<router-link tag="li" class="nav-item nav-dropdown" :to="url" disabled>
<div class="nav-link nav-dropdown-toggle" #click="handleClick"><i :class="icon"></i> {{name}}</div>
<ul class="nav-dropdown-items">
<slot></slot>
</ul>
</router-link>
</template>
<script>
export default {
props: {
name: {
type: String,
default: ''
},
url: {
type: String,
default: ''
},
icon: {
type: String,
default: ''
}
},
methods: {
handleClick(e) {
e.preventDefault();
e.target.parentElement.classList.toggle('open');
}
}
};
</script>
Please help.
The usual way to make a radio-group type controller (where only one item can be selected at once) is to have a variable that indicates which one is selected. Then each element compares itself to the selected one to determine whether it should be in the open state.
Since you have multiple router-links that don't know about each other, the parent object is going to have to own the which-one-is-selected indicator variable. The handleClick of your router-link should $emit an event that the parent will handle by changing the indicator variable. And the router-link should receive the indicator variable as a prop and use it in a computed to set the open class as appropriate.
Your code might look like this:
<template>
<router-link tag="li" class="nav-item nav-dropdown" :class="openClass" :to="url" disabled>
<div class="nav-link nav-dropdown-toggle" #click="handleClick"><i :class="icon"></i> {{name}}</div>
<ul class="nav-dropdown-items">
<slot></slot>
</ul>
</router-link>
</template>
<script>
export default {
props: {
name: {
type: String,
default: '',
selectedItem: Object
},
url: {
type: String,
default: ''
},
icon: {
type: String,
default: ''
}
},
computed: {
openClass() {
return this.selectedItem === this ? 'open' : '';
}
}
methods: {
handleClick(e) {
e.preventDefault();
this.$emit('setSelected', this);
}
}
};
</script>
You can add "itemAttr" property in _nav.js like:
items: [
{
name: 'Dropdown',
url: '/dropdown',
icon: 'icon-grid',
itemAttr: { id: 'drop-1' },
children: [{
name: 'Sub-Item 1',
url: '/dropdown/subitem1'
}, {
name: 'Sub-Item 2',
url: '/dropdown/subitem2'
}, {
name: 'Sub-Item 3',
url: '/dashboard/subitem3'
}]
},
{
name: 'Base',
url: '/base',
icon: 'icon-base',
itemAttr: { id: 'item-1' }
}
]
and in DefaultLayout.js, add event-listeners for click on these two id's, like:
var e1 = document.getElementById("drop-1")
e1.addEventListener("click", function () {
e1.classList.className += " open";
});
var ev1 = document.getElementById("item-1")
ev1.addEventListener("click", function () {
e1.className = "nav-item nav-dropdown"
});
Similarly, you can add more dropdowns and give them id's "drop-2" and "drop-3". OnClick, if you want to open that dropdown list use:
e<i>.classList.className += " open";
and for all the remaining dropdowns that you want to close use:
e<j>.className = "nav-item nav-dropdown";
When clicking on an item you want to close all dropdowns, so use:
e<i>.className = "nav-item nav-dropdown"; //for all the dropdown items.

Implementing sidebar don't displayed

I'm implementing Vue paper dashboard sidebar. So I have something like this:
Into Index I have
<template>
<div>
AdminIndex
<side-bar>
</side-bar>
</div>
</template>
<script>
import { faBox, faImages } from '#fortawesome/fontawesome-free-solid';
import Sidebar from '#/components/sidebar/SideBar';
export default {
name: 'admin-index-view',
components: {
SideBar,
},
data() {
return {
showSidebar: false,
sidebarLinks: [
{
name: 'admin.menu.products',
icon: faBoxes,
route: { name: 'adminProducts' },
},
{
name: 'admin.menu.sliders',
icon: faImages,
route: { name: '/admin/stats' },
},
],
};
},
methods: {
displaySidebar(value) {
this.showSidebar = value;
},
},
};
</script>
SideBar component:
<template>
<div :class="sidebarClasses"
:data-background-color="backgroundColor"
:data-active-color="activeColor">
<!--
Tip 1: you can change the color of the sidebar's background using: data-background-color="white | black | darkblue"
Tip 2: you can change the color of the active button using the data-active-color="primary | info | success | warning | danger"
-->
<!-- -->
<div class="sidebar-wrapper"
id="style-3">
<div class="logo">
<a href="#"
class="simple-text">
<div class="logo-img">
<img src="static/img/vue-logo.png"
alt="">
</div>
Paper Dashboard
</a>
</div>
<slot>
</slot>
<ul :class="navClasses">
<!--By default vue-router adds an active class to each route link. This way the links are colored when clicked-->
<router-link v-for="(link,index) in sidebarLinks"
:key="index"
:to="link.route"
tag="li"
:ref="link.name">
<a>
<font-awesome-icon :icon="link.icon" />
<p v-t="link.name" />
</a>
</router-link>
</ul>
<moving-arrow :move-y="arrowMovePx">
</moving-arrow>
</div>
</div>
</template>
<script>
import FontAwesomeIcon from '#fortawesome/vue-fontawesome';
import MovingArrow from './MovingArrow';
export default {
name: 'side-bar',
components: {
MovingArrow,
FontAwesomeIcon,
},
props: {
type: {
type: String,
default: 'sidebar',
validator: value => {
const acceptedValues = ['sidebar', 'navbar'];
return acceptedValues.indexOf(value) !== -1;
},
},
backgroundColor: {
type: String,
default: 'black',
validator: value => {
const acceptedValues = ['white', 'black', 'darkblue'];
return acceptedValues.indexOf(value) !== -1;
},
},
activeColor: {
type: String,
default: 'success',
validator: value => {
const acceptedValues = [
'primary',
'info',
'success',
'warning',
'danger',
];
return acceptedValues.indexOf(value) !== -1;
},
},
sidebarLinks: {
type: Array,
default: () => [],
},
},
data() {
return {
linkHeight: 60,
activeLinkIndex: 0,
windowWidth: 0,
isWindows: false,
hasAutoHeight: false,
};
},
computed: {
sidebarClasses() {
if (this.type === 'sidebar') {
return 'sidebar';
}
return 'collapse navbar-collapse off-canvas-sidebar';
},
navClasses() {
if (this.type === 'sidebar') {
return 'nav';
}
return 'nav navbar-nav';
},
/**
* Styles to animate the arrow near the current active sidebar link
* #returns {{transform: string}}
*/
arrowMovePx() {
return this.linkHeight * this.activeLinkIndex;
},
},
watch: {
$route() {
this.findActiveLink();
},
},
methods: {
findActiveLink() {
this.sidebarLinks.find((element, index) => {
const found = element.path === this.$route.path;
if (found) {
this.activeLinkIndex = index;
}
return found;
});
},
},
mounted() {
this.findActiveLink();
},
};
</script>
I dont receive any issues or vue errors, sidebar just don't display. In Chrome console just return empty: <side-bar data-v-66018f3c=""></side-bar> Someone knows why sidebar is not binded? What I need to do to get correctly implementation of it? Regards
Chrome console error:
[Vue warn]: Unknown custom element: - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.

Categories