SortableJS / Vue.Draggable

Vue drag-and-drop component based on Sortable.js
https://sortablejs.github.io/Vue.Draggable/
MIT License
20.11k stars 2.9k forks source link

Suggestion: Allow placeholder when target array is empty #480

Open GregPeden opened 6 years ago

GregPeden commented 6 years ago

Just an idea... it would be swell to be able to allow placeholder, perhaps provided as a named slot, which is displayed only when the target array is empty. As soon as an item is dragged in to the 'draggable' field, even before released, the placeholder is no longer rendered. This would appear like the first element being added is going to replace the placeholder upon release.

So one can imagine a note like "drop here to add things" with an infographic icon being in place of nothingness. This secondarily resolves the issue of maintaining a minimum height of the HTML element to allow use when empty.

This past issue discusses a similar need: https://github.com/SortableJS/Vue.Draggable/issues/335

David-Desmaisons commented 6 years ago

In terms of API, using a named slot would totally makes senses. I like this proposal. I will have to take a carefull look to check if this is doable.

ThePendulum commented 6 years ago

It would be convenient to be able to add this placeholder anywhere, not just inside of the target list, and make its disappearance optional, so it also addresses https://github.com/SortableJS/Vue.Draggable/issues/457 more adequately.

satvikpendem commented 5 years ago

I would also second this, especially for nested components.

madebycaliper commented 5 years ago

Any news on this? @David-Desmaisons has this made it into your roadmap for a future version?

David-Desmaisons commented 5 years ago

@madebycaliper still have no good idea on how to implement such a feature. I should not be abble to deliver it in the short/mid term.

madebycaliper commented 5 years ago

@David-Desmaisons thanks for the reply. it seems like a complex feature!

Doogiemuc commented 5 years ago

Think out of the box. You have VUE at hand :-)

    <draggable v-model="list" .....>
       <div v-for="elem in list" :key="elem.order">These are your normal sorted elems</div>
       <p v-if="list.length==0" key="4711">This is shown when container is empty</p>
     </draggable>

(Ok you need a dummy key 4711.)

David-Desmaisons commented 5 years ago

@dominiczaq Well your sample is exactly an example that IS NOT WORKING, because vue.draggable needs the inner children to map the list

madebycaliper commented 5 years ago

@Doogiemuc yea, I've implemented a working solution a number of times, as I use this component in a bunch of projects and had to figure it out. But I think having something standardized that's built into the component itself would make it easier to implement and more future-proof

David-Desmaisons commented 5 years ago

@madebycaliper what is your work-around?

itaishopen commented 5 years ago

@David-Desmaisons I have a solution that works with some glitches <draggable class="listgroup" :class="list._id" v-model="listArray" v-bind="dragOptionsCard" @end="funToMove">

listArray: this.cardList,

cardList() { if (!this.list.cards && this.list.cards.length === 0) { this.list.cards = [] } return this.list.cards },

if you have an empty list the function cardList in computed return an empty list for you to enter the cards in to. in css i gave the empty list a min height of 50 px .listgroup { cursor: move; min-height: 50px; }

that fixed most of the bugs for me

Doogiemuc commented 5 years ago

I totally agree with madebycaliper: There should be a standard solution in Vue.draggable. My "workaround" is working fine as far as I tested it: https://codepen.io/Doogie/pen/qvwjGE

David-Desmaisons commented 5 years ago

@Doogiemuc I agree. Still looking for a reliable solution.

madebycaliper commented 5 years ago

@David-Desmaisons I often use Vuex to fire a DRAG_START and DRAG_END event and then map state and modify the dimensions of the empty Draggable using CSS. I've also tried :

There are oviously lots of custom tactics available, but it would be great if you offered a standardized solution and some supporting logic.

@SirLamer I definitely like the idea of a named slot, as it would be optional and unobtrusive. If the Draggable component could just conditionally show that slot based on items.length, we could pass an element that would extend the list height if it's empty.

johnbamlamb commented 4 years ago

I have had some success using the header slot like this as the last element inside the draggable component: <h3 slot="header" v-if="report.report_modules.length === 0" class="text-secondary">Drop modules here...</h3>

MemeDeveloper commented 4 years ago

I agree that a named slot would be ideal.

However, here's my v simple workaround that's bug free (for me) :

.list-group:empty {
    padding:1rem;
    text-align:center;
}

.list-group:empty:before {
    content: 'Drop files here';
}

This works with

<draggable class="list-group" etc...

MemeDeveloper commented 4 years ago

N.B. above doesn't seem to work when using a transition-group as this adds a span to the "empty" list, meaning the :empty selector doesn't get applied.

MemeDeveloper commented 4 years ago

I got transition-group working with modified css selector and tag="div" as per :

<transition-group type="transition" tag="div">

.list-group:empty,
.list-group > div:empty {
    padding:1rem;
    text-align:center;
}

.list-group:empty:before,
.list-group > div:empty:before {
    content: 'Drop files here';
}

i.e. add a second selector to target the empty div produced by the transition-group

Hope that helps someone out, and thanks for the awesome code !

CheshireCaat commented 2 years ago

Any updates?

adamreisnz commented 2 years ago

@David-Desmaisons this issue was first raised more than 3 years ago. Is it possible to see if this can be implemented in the Vue 3 version?

It doesn't seem like an overly complicated problem to generate/create a cloned copy of the item that is being dragged and have it stay in the original position in the list.

A cloned (ghost) version is already being created, but it's being moved around depending on the mouse position. So another non-moving clone can be created to remain in place where the original item used to sit.

This will make sorting items in the list much calmer to look at as there won't be so much movement happening during the sorting of the item, and it would also fix the webkit hover bug which applies the hover class onto items that are positioned instead of the original item that's being dragged.

simonj commented 2 years ago

+1 for this. This is the feature am working on where i want to have a bigger drag area

https://user-images.githubusercontent.com/183790/147861811-b5d872b8-d5fa-4b6b-948d-821a3da290d5.mov

artemryskal commented 2 years ago

I found a workaround for this webkit browsers bug. For every draggable element you need to add mouseover events which take setHoverEffect function.

setHoverEffect (event) {
  const childList = [].slice.call(event.currentTarget.parentElement.children)

  for (const child of childList) {
    child.classList.remove('hovered')
  }

  if( !childList.some((child) => child.classList.contains('sortable-chosen')) ) {
    event.currentTarget.classList.add('hovered')
  }
}
.hovered {
  background-color: orange;
  color: #fff;
}

Then you need to add mouseleave event which will remove hovered class. event.currentTarget.classList.remove('hovered')

CultivateCreate commented 1 year ago

is this project still active? I see no resolution for this issue, so I'm assuming no?

heavy-matill commented 1 year ago

I found a CSS solution: Add a dummy element in header (or Footer) element with class=hideable-header. Add CSS: .hideable-header:not(:only-child){ display: none; } .hideable-header:only-child{ display: block; } This will result in the element only being shown if it is the only-child of its parent.

In case you have a header or footer already use :first-child or :last-child.

Gyurmatag commented 1 year ago

Any updates?

abejordan commented 7 months ago

How about doing this.

<Draggable class="list-group" :list="list" group="blocks" item-key="uuid">
  <template #header v-if="draft.length == 0">
    What you want to show when the list is empty
    </template>
  <template #item="{ element, index }">
    Your list
</template>
</Draggable>

This essentially hides the header slot when the list has more than 0 elements.