Skip to content

Instantly share code, notes, and snippets.

@jdanyow
Created March 11, 2017 16:35
Show Gist options
  • Save jdanyow/c7acccd9134d2ab272f6c30cee2047ca to your computer and use it in GitHub Desktop.
Save jdanyow/c7acccd9134d2ab272f6c30cee2047ca to your computer and use it in GitHub Desktop.
Aurelia custom checkbox element
<template>
<require from="./mega-check"></require>
<style>
label {
display: block;
margin: 5px;
}
</style>
<h1>${message}</h1>
<label repeat.for="choice of choices">
<mega-check value.bind="choice" checked.bind="selectedChoices" disabled.bind="disabled"></mega-check>
${choice}
</label>
<ul>
<li repeat.for="choice of selectedChoices">${choice}</li>
</ul>
<button click.delegate="selectedChoices.splice(0, selectedChoices.length)">Deselect All</button>
<button click.delegate="selectedChoices = choices.slice(0)">Select All</button>
<button click.delegate="selectedChoices.splice(0, 1)">Deselect One</button>
<button click.delegate="disabled = !disabled">Toggle Disabled</button>
</template>
export class App {
message = 'Hello World!';
choices = ['cake', 'wings', 'whiskey', 'trump is an idiot'];
selectedChoices = [];
disabled = false;
}
<!doctype html>
<html>
<head>
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body aurelia-app>
<h1>Loading...</h1>
<script src="https://jdanyow.github.io/rjs-bundle/node_modules/requirejs/require.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/config.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/aurelia.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/babel.js"></script>
<script>
require(['aurelia-bootstrapper']);
</script>
</body>
</html>
<template>
<style>
.mega-check {
display: inline-block;
border: 2px solid black;
width: 1rem;
height: 1rem;
cursor: pointer;
user-select: none; /* disables text highlighting */
}
.mega-check--checked {
background-color: lightgreen;
}
.mega-check--disabled {
border: 2px solid gray;
}
</style>
<div class="mega-check ${state ? 'mega-check--checked' : ''} ${disabled ? 'mega-check--disabled' : ''}"
aria-checked.bind="state & attr"
tab-index.bind="tabIndex"
click.delegate="clicked()"
keypress.delegate="keypress($event)">
&nbsp;
</div>
</template>
/********************************************************************
* Custom checkbox element
*
* - when the model changes (@bindable checked & @bindable value), the
* visual/aria state of the checkbox is synchronized
*
* - when the checkbox is interacted with (click / spacebar), the model
* is updated
*
* - todo: the html label element has a special relationship with
* inputs... need to replicate that. Clicking on the label isn't selecting
* the custom checkbox.
*
* ******************************************************************/
import {
bindable,
bindingMode,
inject,
BindingEngine,
containerless
} from 'aurelia-framework';
@containerless
@inject(Element, BindingEngine)
export class MegaCheck {
@bindable({ defaultBindingMode: bindingMode.twoWay }) checked = undefined; // public: any[]
@bindable value = null; // public: any
@bindable disabled = false;// public: boolean
@bindable tabIndex = 0; // public: number
element; // private: Element
bindingEngine; // private: BindingEngine
subscription = null; // private: Disposable|null
state = false; // private: boolean
constructor(element, bindingEngine) {
this.element = element;
this.bindingEngine = bindingEngine;
}
checkedChanged() { // public: Array|undefined, Array|undefined
// unsubscribe from the previous array's mutation (eg push/pop/splice)
this.disposeSubscription();
// subscribe to the current array's mutation
if (Array.isArray(this.checked)) {
this.subscription = this.bindingEngine.collectionObserver(this.checked)
.subscribe(() => this.synchronizeView());
}
this.synchronizeView();
}
valueChanged() {
this.synchronizeView();
}
clicked() {
if (this.disabled) {
return;
}
this.state = !this.state;
this.synchronizeModel();
}
keypress(event) { // private event: KeyboardEvent
if (this.disabled || event.ctrlKey || event.altKey || event.shiftKey || event.metaKey || event.which !== 32) {
return;
}
// space key was pressed...
this.state = !this.state;
this.synchronizeModel();
}
synchronizeView() { // private
if (Array.isArray(this.checked)) {
this.state = this.checked.indexOf(this.value) !== -1;
} else {
this.state = false;
}
}
synchronizeModel() {
if (!Array.isArray(this.checked)) {
return;
}
if (this.state && this.checked.indexOf(this.value) === -1) {
this.checked.push(this.value);
} else if (!this.state) {
const index = this.checked.indexOf(this.value);
if (index !== -1) {
this.checked.splice(index, 1);
}
}
}
disposeSubscription() {
if (this.subscription !== null) {
this.subscription.dispose();
this.subscription = null;
}
}
unbind() {
this.disposeSubscription();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment