Open danielxvu opened 1 year ago
Hi @danielxvu yes that would be awesome! Unfortunately I have absolutely no knowledge of Vue myself. If you’re familiar with it would be great if you could help out here 🥂
This is a very rudimentary Vue composable I threw together in Typescript (barely) that's generally working for a project I'm working on. Bear in mind I'm just learning Vue, so I'll be very happy to take feedback and constructive criticism.
Essentially, it creates a new DragSelect instance, provided an array of selectables and a selectable area. It tries to handle allowing for selectables
and area
to be Vue ref()'s (so you can use Vue template refs), straight HTML elements, or Vue components. I haven't done a ton of testing on that front to make sure it works entirely, but it seems to work alright.
If Vue reactive objects get passed in, it should also set up watchers to update the DragSelect instance when they're updated. This is mostly to alleviate issues with template refs getting called before they're available in the template. There's probably a better way to handle this, but it works for now.
I'll try to keep this updated as I find issues/improvements.
/edit: Also, this is a really basic solution. It would probably be beneficial to return an object with the DS instance so that the various DragSelect methods are exposed. This was just an easy way to get it up and running for my needs.
import { unref, isRef, ref, onMounted, onUnmounted, watchEffect } from 'vue'
import DragSelect from 'dragselect'
export function useDragSelect (selectables, area) {
// state encapsulated and managed by the composable
const selected = ref([])
let dragSelectInstance: DragSelect
function initDragSelect () {
// This is all doing some checking/cleanup to allow for different types of input
// selectables is always an array, but it can be an array of HTML elements,
// a vue ref() of HTML elements, or an array of Vue components
// Similarly, area can be either an HTML element or Vue component, ref() or not
if (unref(selectables) && unref(area)) {
const selectableElementArray = unref(selectables).map(el => {
if (el.__isVue) {
return el.$el
} else {
return unref(el)
}
})
let areaElement
if (unref(area).__isVue) {
areaElement = unref(area).$el
} else {
areaElement = unref(area)
}
if (dragSelectInstance instanceof DragSelect) {
dragSelectInstance.stop()
dragSelectInstance.setSettings({
selectables: selectableElementArray,
area: areaElement,
draggability: false
})
dragSelectInstance.start()
} else {
dragSelectInstance = new DragSelect({
selectables: selectableElementArray,
area: areaElement,
draggability: false
})
}
}
}
// if reactive objects like ref() have been passed in, use watchEffect
// to watch and rerun the dragSelect init if they change
if (isRef(selectables) || isRef(area)) {
watchEffect(initDragSelect)
}
function refreshSelected () {
if (dragSelectInstance) {
const dsSelection = dragSelectInstance.getSelection()
selected.value = dsSelection
}
}
// a composable can also hook into its owner component's
// lifecycle to setup and teardown side effects.
onMounted(() => {
initDragSelect()
if (dragSelectInstance) {
dragSelectInstance.subscribe('callback', (callbackObject) => {
refreshSelected()
})
}
})
onUnmounted(() => {
if (dragSelectInstance) dragSelectInstance.stop()
})
// expose managed state as return value
return selected
}
Thanks a lot!! If others can confirm that this is the proper way, I’ll add it to the documentation. As mentioned I’ve no clue myself and would need to learn Vue first to be able to give feedback on it.
In the long run, ideally, my plan is to have DragSelect wrappers for each JavaScript framework in the framework hell 😅
But that will take a while… Unless people knowledgeable in the specific framework are willing to help :)
I've made some updates that were helpful in my use. Mostly, returning more than just the list of selected elements, updating the selected elements on dragmove
instead of callback
, and improved handling of Vue components being sent in as selectables and selection area (the last uses unrefElement from vueuse. There's probably a way to do this without it, but I don't know what it is, and this works well.
I'd also love for someone that knows Vue better than I do to take a look at it, though. :)
import { unref, isRef, ref, onMounted, onUnmounted, watchEffect } from 'vue';
import { unrefElement } from '@vueuse/core';
import DragSelect from 'dragselect';
export function useDragSelect(selectables, area) {
// state encapsulated and managed by the composable
const selected = ref([]);
let dragSelectInstance: DragSelect;
function initDragSelect() {
// This is all doing some checking/cleanup to allow for different types of input
// selectables is always an array, but it can be an array of HTML elements,
// a vue ref() of HTML elements, or an array of Vue components
// Similarly, area can be either an HTML element or Vue component, ref() or not
if (unref(selectables) && unref(area)) {
const selectableElementArray = unref(selectables).map((el) => {
return unrefElement(el);
});
const areaElement = unrefElement(area);
if (dragSelectInstance instanceof DragSelect) {
dragSelectInstance.stop();
dragSelectInstance.setSettings({
area: areaElement,
draggability: false,
});
dragSelectInstance.removeSelectables(
dragSelectInstance.getSelectables(),
true,
true
);
dragSelectInstance.addSelectables(selectableElementArray);
dragSelectInstance.start();
} else {
dragSelectInstance = new DragSelect({
area: areaElement,
selectables: selectableElementArray,
draggability: false,
});
dragSelectInstance.subscribe('dragmove', () => {
refreshSelected();
});
}
}
}
// if reactive objects like ref() have been passed in, use watchEffect
// to watch and rerun the dragSelect init if they change
if (isRef(selectables) || isRef(area)) {
watchEffect(initDragSelect);
}
function getInstance() {
return dragSelectInstance;
}
function clearSelected() {
dragSelectInstance.clearSelection(true);
}
function refreshSelected() {
if (dragSelectInstance) {
const dsSelection = dragSelectInstance.getSelection();
selected.value = dsSelection;
}
}
// a composable can also hook into its owner component's
// lifecycle to setup and teardown side effects.
onMounted(() => {
initDragSelect();
});
onUnmounted(() => {
if (dragSelectInstance) dragSelectInstance.stop();
});
// return composable's managed state as return value
return { selected, clearSelected, instance: getInstance() };
}
Is your feature request related to a problem? Please describe.
Official documentation for usage with Vue.js projects is lacking.
Describe the solution you'd like A guided example on par with the one for React.
Describe alternatives you've considered
I would be happy to contribute a pull request if you are open to reviewing and accepting it.
Additional context
Providing examples of composables based on this library would be ideal, similar to how the React guided example documents how to use this library with useEffect.