Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions common-theme/assets/scripts/label-items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//basically matches keys (labels) to values - can have multiple values on a key.
// this is for classifying activities
// sorting and classifying are fundamental learning strategies
// it's how we make sense of things!
// TODO : make the inverse where we group items into labelled buckets
class LabelItems extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.correctAnswers = new Map();
this.playerAnswers = new Map();
this.labels = [];
this.items = [];
}

connectedCallback() {
this.render();
this.init();
}

render() {
this.shadowRoot.innerHTML = `
<style>
.c-quiz { display: grid;gap: var(--theme-spacing--gutter)}
</style>
<section class="c-quiz">
<slot name="labels"></slot>
<slot name="heading"></slot>
<slot name="content"></slot>
<h4 role="status" aria-live="polite">
<slot name="feedback"></slot>
</h4>
</section>
`;
}

init() {
const labelsSlot = this.shadowRoot.querySelector('slot[name="labels"]');
const contentSlot = this.shadowRoot.querySelector('slot[name="content"]');

labelsSlot.addEventListener("slotchange", () => {
const labels = labelsSlot.assignedElements()[0];
if (!labels) return;
this.setupLabels(labels);
});

contentSlot.addEventListener("slotchange", () => {
const content = contentSlot.assignedElements()[0];
if (!content) return;
this.setupContent(content);
});
}

setupLabels(labelsContainer) {
this.labels = labelsContainer.querySelectorAll("[data-label]");

this.labels.forEach((label) => {
label.setAttribute("draggable", "true");
label.style.cursor = "grab";
label.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", label.dataset.label);
label.style.cursor = "grabbing";
});
});
}

setupContent(contentContainer) {
this.items = contentContainer.querySelectorAll("[data-item]");

this.items.forEach((item) => {
this.correctAnswers.set(item.dataset.item, item.dataset.correct);
this.playerAnswers.set(item.dataset.item, false); // we always start off wrong

item.addEventListener("dragover", (e) => e.preventDefault());
item.addEventListener("drop", (e) => {
e.preventDefault();
const labelId = e.dataTransfer.getData("text/plain");
this.handleDrop(labelId, item);
});
});
}

makeLabel(labelId) {
// make a label
const label = document.createElement("span");
label.textContent = labelId;
label.classList.add("c-label");
return label;
}

handleDrop(labelId, itemElement) {
const itemId = itemElement.dataset.item;
const isCorrect = this.correctAnswers.get(itemId) === labelId;

this.playerAnswers.set(itemId, isCorrect ? true : false);

itemElement.dataset.status = isCorrect ? true : false;
itemElement.classList.toggle("is-good", isCorrect);
itemElement.classList.toggle("is-bad", !isCorrect);

// remove old labels
itemElement.querySelectorAll(".c-label").forEach((label) => label.remove());

// add this label
itemElement.appendChild(this.makeLabel(labelId));

this.updateFeedback(isCorrect);
}

updateFeedback(isCorrect) {
const feedback = this.shadowRoot
.querySelector('slot[name="feedback"]')
.assignedElements()[0];
if (!feedback) return;

const correctCount = [...this.playerAnswers.values()].filter(
(state) => state
).length;
const totalItems = this.items.length;

if (correctCount === totalItems) {
feedback.textContent = "🎉 You've sorted them all!";
} else {
feedback.textContent = `${
isCorrect
? "✅ You got that one right! "
: "❌ You got that one wrong. "
} ${correctCount} of ${totalItems} labelled correctly`;
}
}
}

customElements.define("label-items", LabelItems);
4 changes: 4 additions & 0 deletions common-theme/assets/styles/04-components/label.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
font: 600 var(--theme-type-size--6) system-ui;
border: 1px solid;
border-radius: 1em;

&:not(:has(.c-label__name)) {
padding: 0.125em 0.5em;
}
&__name {
display: inline-block;
padding: 0.125em 0.5em;
Expand Down
54 changes: 54 additions & 0 deletions common-theme/layouts/shortcodes/label-items.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{{/* example usage: attach a label to delimit each item
{{<label-items heading="👆🏾 Drag the labels on to the items 👇🏽"
>}}
[LABEL=Label-1]Item here [LABEL=Label-2]Item here anything you like can be
multiple lines [LABEL=Label-1] Another item here
{{< /label-items >}}
*/}}
{{ $heading := .Get "heading" | default "👆🏾 Drag the labels on to the items 👇🏽" }}
{{/* Split content into items by label, using a similar pattern to TABS and COLUMNS */}}
{{ $content := .Inner | strings.TrimSpace }}
{{ $items := split $content "[LABEL=" }}

{{ $labels := slice }}
{{/* Need to clean up the brackets and stuff */}}
{{ $processedItems := slice }}

{{ range $index, $item := $items }}
{{ if $labelEnd := index (findRE `^([^\]]+)\]` $item) 0 }}
{{ $label := strings.TrimSuffix "]" $labelEnd }}
{{ $labels = $labels | append $label }}

{{ $itemContent := strings.TrimPrefix $labelEnd $item | strings.TrimSpace }}
{{ if $itemContent }}
{{ $processedItems = $processedItems | append (dict "label" $label "content" $itemContent) }}
{{ end }}
{{ end }}

{{ end }}

{{ $labels = $labels | uniq | shuffle }}


<div class="c-block">
<label-items>
<section slot="labels" class="c-labels" aria-label="Labels">
{{ range $labels }}
<button class="c-label" data-label="{{ . | urlize }}">{{ . }}</button>
{{ end }}
</section>
<h3 slot="heading" class="e-heading__5">{{ $heading }}</h3>
<section slot="content" aria-label="Items to label">
{{ range $index, $item := $processedItems }}
<div
data-item="{{ add $index 1 }}"
data-correct="{{ .label | urlize }}">
{{ .content | $.Page.RenderString }}
</div>
{{ end }}
</section>
<section aria-label="Feedback" slot="feedback"></section>
</label-items>
</div>
{{ $labelItems := resources.Get "scripts/label-items.js" | resources.Minify }}
<script src="{{ $labelItems.RelPermalink }}" defer></script>
Loading