I have a view component where I want to be able to drag certain elements (.draggable) onto other certain elements (.droppable) like this:
I can set it up for static <div> elements in mounted() like this:
mounted(){
$('.draggable').draggable({
revert: true
})
$('.droppable').droppable({
hoverClass: 'drop-hover',
drop: function(){
console.log('-= dropped =-')
}
})
}
...
<div class="droppable">Drop Here</div>
<div class="draggable">Drag Me</div>
<div class="draggable">Drag Me</div>
<div class="draggable">Drag Me</div>
But when I want to do this on dynamically in Vue JS with v-for then the jQuery UI bindings seem to get lost:
<div v-for="(item, index) in items">
<div class="droppable">{{ item.name }}</div>
<div v-for="(folder, index) in item.folders" class="draggable">{{ folder.name }}</div>
</div>
I can no longer drag the .draggable items.
As I understand it, jQuery Draggable/Droppable does not work with .on since I suspect one of the issues is that my DOM elements are generated after jQuery is initialized. I tried wrapping my jQuery code in this.$nextTick(() => { ... }) but it still doesn't work.
I have also looked at Vue.Draggable but I want the drag and drop without the simultaneous reordering.
Is it possible to combine jQuery Draggable/Droppable with dynamic data in Vue JS?
Related
I have a simple collapse element in my Vue 3 template:
<template>
<button ref="myCollapse" class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#myCollapse" aria-expanded="false" aria-controls="myCollapse">
Toggle to collapse
</button>
<div class="collapse" id="myCollapse">
collapse content
</div>
</template>
I would like to be able to programatically open and close it. Manually adding/removing the show class does not preserve collapse animations.
Ideally the code would work like this:
<script>
export default {
name: "myCollapse",
methods: {
toggleCollapse() {
this.$refs.myCollapse.toggle()
}
}
}
</script>
Is there a way to achieve this, preferably without using jQuery? Is it correct to assume such way would work for other bootstrap components as well?
I am trying to get all images for a category using Vue
<div class="col-md-12 col-sm-2 p-2">
<a v-on:click="onCategoryManageImageClick($event)" data-target="#location-
category-images">
</span>
</a>
</div>
So the event onCategoryManageImageClick ($event) does not work, if I am adding a html block and then click on get image button.
this is index.js
methods:{
onImagesTabClick(){
this.$root.$emit('activated-tab:location-images');
},
onCategoriesTabClick(){
window.j1App.eventBus.$emit("j1-location-image-list:shown");
},
onCategoryManageImageClick: function(event) {console.log('working event..');
event.preventDefault();
window.j1App.eventBus.$emit("j1-location-category-image-
list:shown",event.currentTarget.id);
}
}
So basically it need to to work like we do in jQuery
$(document).on('click',function{
})
So it works either page load or if adding any new html element on DOM. same I want in Vue.
You cannot alter the Vue template outside of Vue. That won't work. Vue compiles the template once when starting up and adds the event listeners to the rendered elements. If you add elements afterwards, Vue will not know about them.
The correct way of doing this would be to add those new elements in Vue.
<div
class="col-md-12 col-sm-2 p-2"
v-for="item in items"
:key="item.id"
>
<a
v-on:click="onCategoryManageImageClick($event, item)"
data-target="#location-category-images"
>
</a>
</div>
See https://v2.vuejs.org/v2/guide/list.html for documentation. In this case you need the items array variable in data and add more array elements to it, if you need more links.
Try raplacing your a tag with p
<div class="col-md-12 col-sm-2 p-2">
<p v-on:click="onCategoryManageImageClick" data-target="#location-
category-images">
</p>
</div>
I'm doing a project vue and I wanted to know if vue v-if v-else-if time complexity is O(n) or O(1)
I'm using a component in vue like a switch case (takes input and returns html) something like
<div v-if="type === 'A'">
<!-- HTML Data -->
</div>
<div v-else-if="type === 'B'">
<!-- HTML Data -->
</div>
<div v-else-if="type === 'C'">
<!-- HTML Data -->
</div>
<div v-else>
<!-- HTML Data -->
</div>
but with a lot more cases.
Is that the recommended way to do something like this? or should I make a lot of small component and load them dynamically? which is O(1)
And the reason I have this component is that it takes in a string input and returns a custom icon for a data that can be changed, so they have to be loaded dynamically
I think it's better to create a type/value map and a computed property:
new Vue({
el:"#app",
data(){
return{
type: 'A',
types: {A:'A',B:'B',C:'C'}
}
},
computed:{
typeVal(){
return this.types[this.type];
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{typeVal}}
</div>
I have a list of elements, rendered server-side.
Each item has a button that changes its status.
<div class="list">
<div class="list-item" data-is-published="1" data-item-id="11">
<div class="item-link">Item 1</div>
<form method="post" class="list-item-form">
<div class="vue-mount">
<input type="submit" name="changeStatus" class="button-unpublish" value="Unpublish">
<!-- some hidden fields -->
</div>
</form>
</div>
<div class="list-item" data-is-published="1" data-item-id="12">
<div class="item-link">Item 2</div>
<form method="post" class="list-item-form">
<div class="vue-mount">
<input type="submit" name="changeStatus" class="button-unpublish" value="Unpublish">
<!-- some hidden fields -->
</div>
</form>
</div>
</div>
I'm using the following code to replace each item with a vue component
var ms = document.querySelectorAll('.vue-mount')
for (var i = 0; i < ms.length; i++) {
new Vue({
render: h => h(SubscriptionButton)
}).$mount(ms[i])
}
And on the component i get the values of data-is-published and data-item-id and set them on the component
<template>
<input
type="submit"
#click.prevent="changeStatus()"
:value="isPublished === 1 ? 'Unpublish' : 'Publish'">
</template>
<script>
export default {
....
created: function () {
const el = this.$root.$el.parentNode.parentNode
const status = el.dataset.isPublished
this.isPublished = parseInt(status)
this.itemID = el.dataset.itemID
}
}
I'm doing it this way to ensure that it works even if javascript is disabled, but the part this.$root.$el.parentNode.parentNode doesn't feel right.
Is the way I'm doing it ok? Are there better ways to achieve the same?
EDIT
I can put the data attributes on the element that vue will mount to and access them with this.$root.$el.dataset.
What I'm not sure about is how compliant to the best practices the use of this.$root.$el is.
You could use Element.closest (with a polyfill if IE support is required)
const el = this.$root.$el.closest("*[data-is-published]");
That avoids having to know the hierarchy depth of the DOM.
Info on MDN
Edited to add:
The appropriateness of using this.$root.$el depends on how you plan to structure the code. If the element you're trying to find is guaranteed to be a parent of the Vue application, then starting the search from $root is fine. If that bothers you, though, starting the search from this.$el instead would work.
The less conventional part of your approach is that it effectively creates a separate Vue application for each .vue-mount
I want to display all my forms and info pages in floating sidebox.
I don't want to copy and paste the floating sidebox html to all the places. So I want to create a component which acts as container to my forms or info pages.
This is the sample code for form.
<div class="floating-sidebox">
<div class="sidebox-header">
<div class="sidebox-center">
<h3 class="title">{{ title }}</h3>
</div>
</div>
<div class="sidebox-content">
<div class="sidebox-center">
<!-- This is the actual content. Above container code is common for all forms. -->
<vue-form-generator :schema="schema" :model="model" :options="{}"></vue-form-generator>
</div>
</div>
<div class="floating-sidebox-close" #click="cancel"></div>
</div>
<div class="floating-sidebox-overlay"></div>
In above code, I uses vue-form-generator to generate the form. The floating-sidebox elements are common for all forms and info pages. I want to abstract it by Presentational component.
How could I do it Vue2?
Define a component that wraps all your "floating-sidebox" components. You can access the "floating-sideboxes" via this.$children and use their title etc. as navigation placeholder. Since $children is an array you can easily represent the currently visible entity with and index
...
data: function() {
sideboxes: [],
currentIndex: null
},
...
mounted: function() {
this.sideboxes = this.$children;
this.currentIndex= this.$children.length > 0 ? 0 : null;
},
...
computed: {
current: function() {
return this.currentIndex ? this.sideboxes[this.currentIndex] : null;
}
}
You can then bind in the template of the wrapping view
<ul>
<li v-for="(sidebox, index) in sideboxes" #click="currentIndex = index"><!-- bind to prop like title here --></li>
</ul>
<div>
<!-- binding against current -->
</div>
JSfiddle with component https://jsfiddle.net/ptk5ostr/3/