Every year, my wife's book club has a book exchange during the holidays. Due to the need to maintain physical distance in 2020, I created an online gift exchange for them to use during a book club videoconference. Apparently, the virtual book exchange worked out (at least, I received kind compliments from the book club members), so I decided to share this simple little hack.
How the book exchange usually works
In past years, the exchange has gone something like this:
- Each person buys a book and wraps it up.
- Everyone arrives at the host's home and puts the wrapped books in a pile.
- Each person draws a number out of a hat, which establishes their turn.
- The person who drew No. 1 selects a book from the pile and unwraps it. In turn, each subsequent person chooses to either take a wrapped book from the pile or to steal an unwrapped book from someone who has gone before.
- When someone's book is stolen, they can either replace it with a wrapped book from the pile or steal another book (but not the one that was stolen from them) from someone else.
- And so on… eventually, someone has to take the last unwrapped book to end the exchange.
Designing the virtual book exchange
My first decision was which implementation platform to use for the book exchange. Because there would already be a browser open to host the videoconference, I decided to use HTML, CSS, and JavaScript.
Then it was design time. After some thinking, I decided to use rectangles to represent the book club members and the books. The books would be draggable, and when one was dropped on a member's rectangle, the book would unwrap (and stay unwrapped). I needed some "wrapping paper," so I used this source of free-to-use images.
I took screenshots of the patterns I liked and used GIMP to scale the images to the right width and height.
I needed a way to handle draggable and droppable interactions; given that I've been using jQuery and jQuery UI for several years now, I decided to continue along that path.
For a while, I struggled with what a droppable element should do when something was dropped on it. Finally, I realized that all I needed to do was unwrap the dropped item (if it was still wrapped). I also spent some time fretting over how to lay stuff out until I realized that the easiest thing to do was just leave the elements floating.
Jumping to the results, here's a screenshot of the user interface at the beginning of the exchange:
There are nine book club members: Wanda, Carlos, Bill, and so on. There are also nine fairly ugly wrapped parcels.
Let's say Wanda goes first and chooses the flower wrapping paper. The host clicks and drags that parcel to Wanda's name, and the parcel unwraps:
Whoops! That title and author are a bit too long to fit on the book's "cover." Oh well, I'll fix that in the next version.
Carlos is next. He decides he really wants to read that book, so he steals it. Wanda then chooses the paisley pattern, and the screen looks like this:
And so on until the exchange ends.
The code
So what about the code? Here it is:
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Book Exchange</title>
6 <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">
7 <style>
8 .draggable {
9 float: left;
10 width: 90px;
11 height: 90px;
12 background: #ccc;
13 padding: 5px;
14 margin: 5px 5px 5px 0;
15 }
16 .droppable {
17 float: left;
18 width: 100px;
19 height: 125px;
20 background: #999;
21 color: #fff;
22 padding: 10px;
23 margin: 10px 10px 10px 0;
24 }
25 </style>
26 <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
27 <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
28 </head>
29 <body>
30 <h1 style="color:#1a1aff;">Raffles Book Club Remote Gift Exchange</h1>
31 <h2 style="color:#aa0a0a;">The players, in random order, and the luxurious gifts, wrapped:</h2>
32
33 <div>
34 <div id="wanda" class="droppable">Wanda</div>
35 <div id="carlos" class="droppable">Carlos</div>
36 <div id="bill" class="droppable">Bill</div>
37 <div id="arlette" class="droppable">Arlette</div>
38 <div id="joanne" class="droppable">Joanne</div>
39 <div id="aleks" class="droppable">Alekx</div>
40 <div id="ermintrude" class="droppable">Ermintrude</div>
41 <div id="walter" class="droppable">Walter</div>
42 <div id="hilary" class="droppable">Hilary</div>
43 </div>
44 <div>
45 <div id="bows" class="draggable" style="background-image: url('bows.png');"></div>
46 <div id="boxes" class="draggable" style="background-image: url('boxes.png');"></div>
47 <div id="circles" class="draggable" style="background-image: url('circles.png');"></div>
48 <div id="gerbers" class="draggable" style="background-image: url('gerbers.png');"></div>
49 <div id="hippie" class="draggable" style="background-image: url('hippie.png');"></div>
50 <div id="lattice" class="draggable" style="background-image: url('lattice.png');"></div>
51 <div id="nautical" class="draggable" style="background-image: url('nautical.png');"></div>
52 <div id="splodges" class="draggable" style="background-image: url('splodges.png');"></div>
53 <div id="ugly" class="draggable" style="background-image: url('ugly.png');"></div>
54 </div>
55
56 <script>
57 var books = {
58 'bows': 'Untamed by Glennon Doyle',
59 'boxes': "The Heart's Invisible Furies by John Boyne",
60 'circles': 'The Great Halifax Explosion by John Bacon',
61 'gerbers': 'Homes: A Refugee Story by Abu Bakr al Rabeeah, Winnie Yeung',
62 'hippie': 'Before We Were Yours by Lisa Wingate',
63 'lattice': "Hamnet and Judith by Maggie O'Farrell",
64 'nautical': 'Shuggy Bain by Douglas Stewart',
65 'splodges': 'Magdalena by Wade Davis',
66 'ugly': 'Funny Boy by Shyam Selvadurai'
67 };
68 $( ".droppable" ).droppable({
69 drop: function(event, ui) {
70 var element = $(ui.draggable[0]);
71 var wrapping = element.attr('id');
72 /* alert( $(this).text() + " got " + wrapping); */
73 $(ui.draggable[0]).css("background-image","url(book_cover.png)");
74 $(ui.draggable[0]).text(books[wrapping]);
75 },
76 out: function() {
77 /* alert( $(this).text() + " lost it" ); */
78 }
79 });
80 $( ".draggable" ).draggable();
81 </script>
82
83 </body>
84 </html>
Breaking it down
Let's go over this code bit by bit.
- Lines 1–6: Upfront, I have the usual HTML boilerplate,
HTML
,HEAD
,META
,TITLE
elements, followed by a link to the CSS for jQuery UI. - Lines 7–25: I added two new style classes:
draggable
anddroppable
. These define the layout for the books (draggable) and the people (droppable). Note that, aside from defining the size, background color, padding, and margin, I established that these need to float left. This way, the layout adjusts to the browser window width in a reasonably acceptable form. - Line 26–27: With the CSS out of the way, it's time for the JavaScript libraries, first jQuery, then jQuery UI.
- Lines 29–83: Now that the
HEAD
element is done, next is theBODY
:- Lines 30–31: These couple of titles,
H1
andH2
, let people know what they're doing here. - Lines 33–43: A
DIV
to contain the people:- Lines 34–42: The people are defined as droppable
DIV
elements and givenID
fields corresponding to their names.
- Lines 34–42: The people are defined as droppable
- Lines 44–54: A
DIV
to contain the books:- Lines 45–53: The books are defined as draggable
DIV
elements. Each element is declared with a background image corresponding to the wrapping paper with no text between the<div>
and</div>
. TheID
fields correspond to the wrapping paper.
- Lines 45–53: The books are defined as draggable
- Lines 56–81: These contain JavaScript to make it all work.
- Lines 57–67: This JavaScript object contains the book definitions. The keys (
'bows'
,'boxes'
, etc.) correspond to theID
fields of the bookDIV
elements. The values ('Untamed by Glennon Doyle',
"The Heart's Invisible Furies by John Boyne"
, etc.) are the book titles and authors. - Lines 68–79: This JavaScript jQuery UI function defines the droppable functionality to be attached to HTML elements whose class is
droppable
. - Lines 69–75: When a
draggable
element is dropped onto adroppable
element, the functiondrop
is called.- Line 70: The
element
variable is assigned the draggable object that was dropped (this will be a<div id="..." class="draggable"...></div>
element. - Line 71: The
wrapping
variable is assigned the value of theID
field in the draggable object. - Line 72: This line is commented out, but while I was learning and testing, calls to
alert()
were useful. - Line 73: This reassigns the draggable object's background image to a bland image on which text can be read; part 1 of unwrapping is getting rid of the wrapping paper.
- Line 74: This sets the text of the draggable object to the title of the book, looked up in the book's object using the draggable object's ID; part 2 of the unwrapping is showing the book title and author.
- Line 70: The
- Lines 76–78: For a while, I thought I wanted something to happen when a draggable object was removed from a droppable object (e.g., when a club member stole a book), which would require using the
out
function in a droppable object. Eventually, I decided not to do anything. But, this could note that the book was stolen and make it "unstealable" for one turn; or it could show a status line that says something like: "Wanda's book Blah Blah by Joe Blogs was stolen, and she needs to choose another." - Line 80: This JavaScript jQuery UI function defines the draggable functionality to be attached to HTML elements whose class is
draggable
. In my case, the default behavior was all I needed.
- Lines 57–67: This JavaScript object contains the book definitions. The keys (
- Lines 30–31: These couple of titles,
That's it!
A few last thoughts
Libraries like jQuery and jQuery UI are incredibly helpful when trying to do something complicated in JavaScript. Look at the $().draggable()
and $().droppable()
functions, for example:
$( ".draggable" ).draggable();
The ".draggable"
allows associating the draggable()
function with any HTML element whose class is "draggable." The draggable()
function comes with all sorts of useful behavior about picking, dragging, and releasing a draggable HTML element.
If you haven't spent much time with jQuery, I really like the book jQuery in Action by Bear Bibeault, Yehuda Katz, and Aurelio De Rosa. Similarly, jQuery UI in Action by TJ VanToll is a great help with the jQuery UI (where draggable and droppable come from).
Of course, there are many other JavaScript libraries, frameworks, and what-nots around to do good stuff in the user interface. I haven't really started to explore all that jQuery and jQuery UI offer, and I want to play around with the rest to see what can be done.
Comments are closed.