Creating a Sorting Game
Tags: litelement, webcomponents, haxtheweb, javascript
Published: 2021-12-13 03:42:04

## Overview
I had the opportunity to work with a team of fellow developers to create a fully-fledged, interactive component as the culimation of our efforts learning component-based design and development using JavaScript. Using [LitElement](https://lit.dev/) as our JS web component library, we developed our project with the intent of creating a versatile component that could be adopted for use within the Open-source [Hax-the-Web](https://haxtheweb.org/) project.

Our team decided to create a sorting game where users are able to sort items in a list-based fashion, either by dragging or via buttons, until the correct order is achieved.

Here is the [NPM](https://www.npmjs.com/package/@table-in-the-corner/project-3) link if you are interested!

This is the original sorting game we targeted to reproduce as a web component: "[Sort the Paragraphs](https://h5p.org/content-types/sort-the-paragraphs)"

### Buttons
![Button use gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/46e6f1hk8korbr3m7e8v.gif)

### Dragging

![Dragging options gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju9gurnxuphc8xe703q0.gif)



## Tutorial
In order to develop this sorting game, our implementation fell into 2 primary elements within the component. One is the frame (SortableFrame) and the other is the reusable option card (SortableOption). 

Dragging was particularly challenging for myself specifically to try to wrap my head around. Initially, I went down a very difficult path where I was attempting to great one universal event listener in the SortableFrame that would handle an option every time a drag was started. This meant that I was creating a sortable option, and below each card was an associated droppable area. I then intended to toggle this droppable area depending on whether the card actively being dragged was dropped within the bounds of that drop area.

The problem with this approach was that it was very difficult to discern different types of drag events (dragstart, dragend, drag, drop, etc.). It was also a haggle to re-append drop zones once an option card was moved in the list. I was warned that the edge cases for this approach would be incredibly tedious, so instead our team took the route of applying one drag handler to the sortable option that could be reused within each option. This way, it would only be triggered when that element itself began its drag.

  ```js
  // The Mouse position, drag position, and offSetTop logic was taken nearly directly from Sean's SlimeSorting Implementation
// The overall idea of how to go about dragging to sort each option was taken from Sean as well

drag(ev) {
const pos = ev.clientY;
let currentIndex =  0;
this.dragPosition = this.position - this.offsetTop;
if (pos !==  0) {
	this.position = pos;
}
for (let index =  0; index < this.parentElement.children.length; index +=  1) {
	if (this === this.parentElement.children[index]) {
		currentIndex = index;
	}
	if (window.innerHeight - this.parentElement.clientHeight <  300) {
		if (this.offsetTop - this.position >  0) {
			// https://stackoverflow.com/questions/9732624/how-to-swap-dom-child-nodes-in-javascript
			// https://stackoverflow.com/questions/4793604/how-to-insert-an-element-after-another-element-in-javascript-without-using-a-lib
			this.parentElement.insertBefore(this, this.parentElement.children[currentIndex]);
		}
		if (this.offsetTop - this.position <  40) {
			this.parentElement.insertBefore(this, this.parentElement.children[currentIndex +  1].nextElementSibling);
		}
	} else {
		if (this.offsetTop - this.position >  40) {
			this.parentElement.insertBefore(this, this.parentElement.children[currentIndex]);
		}
		if (this.offsetTop - this.position <  -60) {
			this.parentElement.insertBefore(this,this.parentElement.children[currentIndex +  1].nextElementSibling);
		}
	}
	this.disable();
	}
}
 ```
I want to give major credit to Sean for not only discovering how to use offsetTop and the cursor's current Y position to calculate the distance dragged before a dragged item should move, but for also taking the time to explain to me how he came about his solution and why it works. All credit goes to him for discovering it and allowing me to use it as well. I really struggled to implement something similar on my own volition. While I would have liked to find a novel solution myself, I ended up following his core cursor position and drag calculation detection logic as the structure of my team's draggable component logic.  Please check out [Sean and his group's implemention](https://github.com/Viable-Slime/slime-the-web/blob/f4f475b61416b1551943214e98c7062e29d04dde/src/SlimeSortingOption.js#L52) of this card sorting logic, all credit goes to him.

I then went in and worked on editing the parameters to detect our desired drag distance, as well as attempted to put in some additional handling related to how far your have to drag your cursor on smaller screens where not all options may be able to fit on the screen at once.

Another novel approach I thought was really cool was the use of the [Fisher-Yates (Knuth) Shuffle algorithm](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle), which was suggested to me via StackOverflow when I was trying to find the best way to randomize the game's options when you first start to play. [Here](https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array) is the source I duplicated to implement this sorting algorithm.
```js
// Fisher-Yates (Knuth) Shuffle
// https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array

let currentIndex = question.answers.length;
let randomIndex;
// While there remain elements to shuffle...
while (currentIndex !==  0) {
// Pick a remaining element...
	randomIndex =  Math.floor(Math.random() * currentIndex);
	currentIndex -=  1;
	// And swap it with the current element.
	[this.randomized[currentIndex], this.randomized[randomIndex]] = [this.randomized[randomIndex],this.randomized[currentIndex],];
}
```

Finally, it was also interesting how we ended up disabling the up arrow on the upper-most option and the down arrow on the lowest option. It was suggested to use nextElementSibling as opposed to our original use of nextSibling. nextSibling can actually return a blank, whitespace TextNode as the "next sibling" when items are injected into the DOM, so it was really interesting to come across this issue. [This](https://newbedev.com/whats-the-difference-between-nextelementsibling-vs-nextsibling) is an explanation. Here is the function for disabling the down arrow:

```js
downbtn() {
	if (this.nextElementSibling !=  null) {
		const after = this.nextElementSibling;
		document.querySelector('sortable-frame').shadowRoot.querySelector('.frame').querySelector('#options').querySelectorAll('sortable-option')
		.forEach(option  => {option.shadowRoot.querySelectorAll('button')
		.forEach(but  => {
			// eslint-disable-next-line no-param-reassign
			but.disabled =  false;
			});
		});
		this.parentNode.insertBefore(after, this);
		this.disable();
	}
}
```

## Resources
Our team used a number of very helpful resources to assist with our implementation. As suggested by other developers, [this](https://codepen.io/andyvanee/pen/KKKaOmw) CodePen was suggested as one possible way we could re-engineer the sorting aspect of the component. This implementation actually uses an older version of LitElement, so it was very interesting to use this as a baseline even though we really didn't go down this route a ton.

Another cool implementation I found, one that focused more on the design side of a sorting game than the development side, was [this awesome post](https://www.darins.page/articles/designing-a-reorderable-list-component#demo) put out by Darin Senneff on 11/8/2021. This design is leagues above our implementation, so I highly recommend checking it out if you want to make your own insanely thorough component. 

Another reference I used was suggested to me by a fellow developer as a way to import and process JSON files containing user-generated questions and answers. Using the Hax-the-Web support for modular web components, our team used the [HAXSchema](https://dev.to/btopro/understanding-haxschema-the-api-powering-our-editor-2ln6) to wire-up this game to support user-generated questions. Following a standard JSON format, users who reference a local JSON file containing their questions and answers will be able to support their own sorting game questions! [This](https://github.com/elmsln/lrnwebcomponents/blob/master/elements/csv-render/src/csv-render.js#L284) is the function that was sent to me to help me reverse engineer some support for reading JSON files into the component.

In the project, we also used the [SimpleIcon](https://www.npmjs.com/package/@lrnwebcomponents/simple-icon) and SimpleIconLite libraries for the arrow icons for the buttons on our card options. [Here](https://dev.to/btopro/making-a-simple-icon-web-component-3bc9) is a great tutorial.

## CodePen and Github links

{% codepen https://codepen.io/nro337/pen/OJxbObr %}
Note: Dragging kinda works in the embedded CodePen, open the link to fully interact!

{% github https://github.com/table-in-the-corner/project-3 %}

[NPM Link](https://www.npmjs.com/package/@table-in-the-corner/project-3): https://www.npmjs.com/package/@table-in-the-corner/project-3

Don't hesitate to reach out if you have any questions. Thanks for your interest!