(That is is one of our recent set lists: I never claimed we were cool - we're called The Love Handles after all.)
The full list of songs (minus any already selected) appears in the first list. Then there are 4 more lists corresponding to sets (we usually have 2 sets, with a 3rd to hold "spares" in case we under-run or get asked to play on. Occasionally we play 3 sets.) I can now:
- Add a song to a set by dragging it from the Songs list to the desired Set list
- Remove a song from a set by dragging it from the Set list back to the Songs list
- Move songs from one set to another by drag and drop
- Move a song up and down within a set by drag and drop
It really was incredibly simple to achieve. First I needed to include the relevant jQuery library into my page:
#IMAGE_PREFIX#libraries/jquery-ui/1.8.22/ui/minified/jquery.ui.sortable.min.js
(This is shipped with APEX but not included in applications by default.)
Then I constructed the HTML for each list as follows:
<ul class="connectedSortable">
<li class="set1"><input type="hidden" name="f01" value="123" />Land of 1000 Dances</li>
...
</ul>
<input type="hidden" name="f01" value="0" />
Explanation:
- Each of the 5 lists has the same class "connectedSortable". This is what allows us to drag and drop between them.
- Each list item consists of a hidden item containing the song's ID in the database, and the song title to be displayed. All the hidden items have name="f01" so that they will all appear in an APEX array g_f01 when the page is submitted. (The class "set1" is only there so that I can colour each list differently.)
- After each list I put another hidden item also named "f01" with a value of 0. This acts as a separator between the lists when processing the g_f01 array - if the value is 0 I know that I have reached the end of a list and am starting the next.
- Originally, I built each list as a report region, with a bespoke region and report template. But after a while I realised it was simpler to use PL/SQL regions to dynamically render the HTML.
Now for the Javascript that makes it all work:
$(function() {
$( ".connectedSortable" ).sortable({
connectWith: ".connectedSortable"
}).disableSelection();
});
That's it - just that.
Finally I just had to write some PL/SQL to process the array when the page is submitted:
declare
l_set_no integer := 0;
begin
apex_collection.create_or_truncate_collection ('SETLIST_EDITOR');
for i in 1..apex_application.g_f01.count
loop
if apex_application.g_f01(i) = 0 then
-- End of set
l_set_no := l_set_no + 1;
else
-- Song: add to current set
apex_collection.add_member
( 'SETLIST_EDITOR'
, l_set_no
, apex_application.g_f01(i)
);
end if;
end loop;
end;
I'm using a collection here, which I populated on initial page load from the database. I could have worked on the set list table directly, but this allows me to submit the page if needed for other reasons without saving or losing the changes.
You can see the final result on this apex.oracle.com demo.
I think jQuery is now my second-favourite developer tool (after APEX of course).