Sortable Widget

I want to build a page where which sortable elements (similiar to something like: Sortable | jQuery UI).

Then I want to be able to get the current order of those items to register watchers on that. Would this be possible with panel?

Hi @kring

You can use ReactiveHTML to wrap any js library.

I would recommend wrapping a non-jquery based one like SortableJS.

Try to build some small code that works, iterate from there and post your questions below :+1:

that looks great, will cheeck it out

1 Like

ReactiveHTML looks really powerfull, but I somehow don’t get it to work:

import panel as pn
pn.extension(js_files={'sortablelist': "https://raw.githack.com/SortableJS/Sortable/master/Sortable.js"})#"http://SortableJS.github.io/Sortable/Sortable.js"})

from panel.reactive import ReactiveHTML
import param
class Slideshow(ReactiveHTML):
    
    index = param.Integer(default=0)
    
    _template = """
  <div id="abc">title</div>
  <div id="simpleList" class="list-group">
    <div class="list-group-item">This is <a href="http://rubaxa.github.io/Sortable/">Sortable</a></div>
    <div class="list-group-item">It works with Bootstrap...</div>
    <div class="list-group-item">...out of the box.</div>
    <div class="list-group-item">It has support for touch devices.</div>
    <div class="list-group-item">Just drag some elements around.</div>
  </div>
     """
    _scripts = {
     'after_layout': """
    var el = document.getElementById('simpleList');
    var title = document.getElementById('abc');
    console.log("hallo", el, title)
    Sortable.create(el, { /* options */ });
     """
   }
        
Slideshow(width=800, height=300)

For some reason the document.getElementById does not find my HTML nodes. Any ideas why?

Hi @kring

If you inspect the html you will see that your ids are appended an integer. That is to make them unique if inserted multiple times.

But instead of using document.getElementById('simpleList') you should be able to refer to simpleList directly.

So you can do like below.

import panel as pn
pn.extension(js_files={'sortablelist': "https://raw.githack.com/SortableJS/Sortable/master/Sortable.js"})#"http://SortableJS.github.io/Sortable/Sortable.js"})

from panel.reactive import ReactiveHTML
import param
class Slideshow(ReactiveHTML):

    index = param.Integer(default=0)

    _template = """
  <div id="abc">title</div>
  <div id="simpleList" class="list-group">
    <div class="list-group-item">This is <a href="http://rubaxa.github.io/Sortable/">Sortable</a></div>
    <div class="list-group-item">It works with Bootstrap...</div>
    <div class="list-group-item">...out of the box.</div>
    <div class="list-group-item">It has support for touch devices.</div>
    <div class="list-group-item">Just drag some elements around.</div>
  </div>
     """
    _scripts = {
     'after_layout': """
    console.log("hallo", simpleList, abc)
    Sortable.create(simpleList, { /* options */ });
     """
   }

Slideshow(width=800, height=300).servable()

1 Like

Next steps are probably configuration and reacting to events https://github.com/SortableJS/Sortable#options. Looking forward to see that in action :slight_smile:

Thanks a lot, got it to work :slight_smile:


import param
import panel as pn
pn.extension(js_files={'sortablelist': "https://raw.githack.com/SortableJS/Sortable/master/Sortable.js"})

class Sortable(pn.reactive.ReactiveHTML):

    rows = param.List(default=[0,1,2,3,4,5])

    _template = """
      <div id="simpleList" class="list-group">
          {% for row in rows %}
            <div class="list-group-item">Row number ${row}</div>
          {% endfor %}
      </div>
      <button id="save_btn" type="button" onclick="${save}">Save</button>
     """
    
    _scripts = {
     'after_layout': """Sortable.create(simpleList, {
         onEnd: function(evt) {
            var element = data.rows[evt.oldIndex];
            list = data.rows
            list.splice(evt.oldIndex, 1);
            list.splice(evt.newIndex, 0, element);
            data.rows = list
        }
     });"""
   }
    
    def save(self, event):
        print("Do some save magic with: ", self.rows)
        
s = Sortable(width=800, height=300)
s.servable()
1 Like

Awesome. I made a version that is a bit more general just in case someone needs it.

import param
import panel as pn

class Sortable(pn.reactive.ReactiveHTML):
    rows = param.List()

    _template = """
      <div id="simpleList" class="list-group">
          {% for row in rows %}
            <div class="list-group-item">${row}</div>
          {% endfor %}
      </div>
     """

    _scripts = {
      'after_layout': """Sortable.create(simpleList, {
          onEnd: function(evt) {
              var element = data.rows[evt.oldIndex];
              list = data.rows
              list.splice(evt.oldIndex, 1);
              list.splice(evt.newIndex, 0, element);
              data.rows = list
          }
      });"""
    }

    __javascript__=[
      "https://raw.githack.com/SortableJS/Sortable/master/Sortable.js"
    ]

pn.extension(sizing_mode="stretch_width")

s = Sortable(rows=[
  "Panel is awesome 0",
  "Panel is awesome 1",
  "Panel is awesome 2",
  "Panel is awesome 3",
  "Panel is awesome 4",
  "Panel is awesome 5",
])

pn.template.FastListTemplate(
  site="Awesome Panel",
  title="Sortable Rows",
  main=[pn.Column(s, s.param.rows)],
  header_accentbasecolor="#f3e4df").servable()
1 Like