Skip to content

Instantly share code, notes, and snippets.

@RomkeVdMeulen
Last active August 17, 2018 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RomkeVdMeulen/29b8818fb673175bf316eb28da9a32ae to your computer and use it in GitHub Desktop.
Save RomkeVdMeulen/29b8818fb673175bf316eb28da9a32ae to your computer and use it in GitHub Desktop.
An Aurelia component showing a textarea with line-numbers and locked height
<template>
<div class="element-wrapper ${invoerFocus ? 'focus' : ''} ${invoerRegels.length >= 10 ? 'vol' : ''}"
e2e="wrapper" data-focus.bind="invoerFocus">
<ol class="regelnummers" ref="regelnummers" e2e="regelnummer-wrapper" css="height: ${hoogte + 24}px">
<li repeat.for="i of aantalGetoondeRegels" e2e="regelnummer"
data-actief.bind="invoerRegels.length > i && invoerRegels[i].trim() !== ''"
class="regelnummer ${invoerRegels.length > i && invoerRegels[i].trim() !== '' ? 'actief' : ''}"></li>
</ol>
<div class="invoer-wrapper">
<textarea value.bind="invoer" ref="invoerElement" e2e="invoer"
placeholder="${placeholder}" aria-labelledby.bind="ariaLabelledby"
focus.bind="invoerFocus" css="height: ${hoogte}px"></textarea>
</div>
</div>
</template>
import {bindable, bindingMode, computedFrom, observable, TaskQueue} from "aurelia-framework";
import {autoinject} from "aurelia-property-injection";
export class Tekstvlak {
private readonly REGELNUMMERS_MINIMUM = 11;
@autoinject
private taskQueue: TaskQueue;
@bindable
ariaLabelledby?: string;
@bindable({defaultBindingMode: bindingMode.twoWay})
invoer: string;
@bindable
placeholder?: string;
aantalGetoondeRegels = this.REGELNUMMERS_MINIMUM;
@observable
regelnummers: HTMLElement;
hoogte: number;
@computedFrom("invoer")
get invoerRegels() {
return this.invoer.split("\n");
}
regelnummersChanged() {
this.taskQueue.queueTask(this.updateHoogte.bind(this));
}
invoerChanged() {
this.updateHoogte();
}
private updateHoogte() {
if (!this.regelnummers) {
return;
}
const regel = <HTMLElement> this.regelnummers.querySelector(":first-child");
const regelhoogte = regel.offsetHeight;
this.aantalGetoondeRegels = Math.max(this.REGELNUMMERS_MINIMUM, this.invoerRegels.length);
this.hoogte = this.aantalGetoondeRegels * regelhoogte;
}
}
numbered-textarea {
display: block;
&::after {
content: '';
display: block;
clear: both;
}
.element-wrapper {
height: 224px;
overflow-x: hidden;
overflow-y: hidden;
border: 1px solid $grijs-licht;
border-radius: 8px 0 0 8px;
&.focus {
outline: 3px solid $vevida-groen;
outline-offset: 1px;
}
&.vol {
overflow-y: scroll;
}
.regelnummers {
float: left;
height: 244px;
overflow: hidden;
margin: 0;
padding: 10px 5px 0 40px;
background-color: $grijs-licht;
line-height: 20px;
.regelnummer:not(.actief) {
color: $grijs-donker;
}
}
.invoer-wrapper {
float: right;
overflow: hidden;
width: calc(100% - 47px);
padding: 10px;
textarea {
width: 100%;
height: 220px;
padding: 0;
overflow: hidden;
resize: none;
border: none;
outline: none;
background: none;
line-height: 20px;
}
}
}
}
import {UiPart} from "bestelapp/component/helpers/uipart";
export class TekstvlakUiPart extends UiPart {
private scopeOverride?: HTMLElement;
private get scope() {
return this.scopeOverride ? this.scopeOverride : this.component.element;
}
setScope(scope: HTMLElement) {
this.scopeOverride = scope;
}
get wrapper() {
const wrapper = this.scope.querySelector('[e2e="wrapper"]')!;
return {
get aanwezig() {
return wrapper !== null;
},
get toontFocus() {
return wrapper.getAttribute("data-focus") === "true";
},
};
}
get regelnummerWrapper() {
const wrapper = <HTMLElement> this.scope.querySelector('[e2e="regelnummer-wrapper"]');
return {
get styleHoogte() {
return wrapper.style.height!;
},
};
}
get regelnummers() {
return [...this.scope.querySelectorAll('[e2e="regelnummer"]')].map(rn => ({
get actief() {
return rn.getAttribute("data-actief") === "true";
},
}));
}
get invoer() {
const invoer = <HTMLTextAreaElement> this.scope.querySelector('[e2e="invoer"]');
return {
get aanwezig() {
return invoer !== null;
},
get value() {
return invoer.value;
},
get styleHoogte() {
return invoer.style.height!;
},
get placeholder() {
return invoer.placeholder;
},
get ariaLabelledby() {
return invoer.getAttribute("aria-labelledby");
},
get gefocust() {
return invoer.matches(":focus");
},
vulIn(waarde: string) {
invoer.focus();
invoer.value = waarde;
invoer.dispatchEvent(new Event("change"));
},
};
}
}
import {TekstvlakUiPart} from "bestelapp/component/aurelia/componenten/form/tekstvlak.part";
import {TekstvlakTest} from "bestelapp/component/aurelia/componenten/form/tekstvlak.test";
import {expect} from "chai";
describe("aurelia/aurelia/componenten/form/tekstvlak", () => {
let test: TekstvlakTest, tekstvlak: TekstvlakUiPart;
beforeEach(() => {
test = new TekstvlakTest();
test.setUp();
tekstvlak = test.uiPart;
});
afterEach(() => test.tearDown());
it("toont een invoerveld met regelnummers die aangeven welke regels ingevuld staan", async () => {
await test.start();
expect(tekstvlak.wrapper.aanwezig).to.be.true;
expect(tekstvlak.wrapper.toontFocus).to.be.false;
expect(tekstvlak.invoer.aanwezig).to.be.true;
expect(tekstvlak.invoer.gefocust).to.be.false;
expect(tekstvlak.invoer.placeholder).to.equal(test.placeholder);
expect(tekstvlak.invoer.ariaLabelledby).to.equal(test.ariaLabelledby);
expect(tekstvlak.invoer.value).to.be.empty;
expect(tekstvlak.regelnummers).to.be.an("array").with.lengthOf.at.least(1);
test.invoer = "een\ntwee";
await test.wachtOpRender();
expect(tekstvlak.invoer.value).to.equal("een\ntwee");
expect(tekstvlak.invoer.gefocust).to.be.false;
expect(tekstvlak.regelnummers.slice(0, 2)).to.deep.equal([
{actief: true},
{actief: true},
]);
tekstvlak.invoer.vulIn("1\n \n3\n");
await test.wachtOpRender();
expect(test.invoer).to.equal("1\n \n3\n");
expect(tekstvlak.regelnummers.slice(0, 4)).to.deep.equal([
{actief: true},
{actief: false},
{actief: true},
{actief: false},
]);
});
it("past de hoogte van het invoerveld en de regelnummers aan op het aantal regels", async () => {
await test.start();
test.invoer = " ";
await test.wachtOpRender();
const rnHoogteInitieel = parseInt(tekstvlak.regelnummerWrapper.styleHoogte, 10);
const tvHoogteInitieel = parseInt(tekstvlak.invoer.styleHoogte, 10);
expect(rnHoogteInitieel).to.be.greaterThan(0);
expect(tvHoogteInitieel).to.be.greaterThan(0);
test.invoer = Array(20).join("a\n");
await test.wachtOpRender();
expect(parseInt(tekstvlak.regelnummerWrapper.styleHoogte, 10)).to.be.greaterThan(rnHoogteInitieel);
expect(parseInt(tekstvlak.invoer.styleHoogte, 10)).to.be.greaterThan(tvHoogteInitieel);
test.invoer = " ";
await test.wachtOpRender();
expect(parseInt(tekstvlak.regelnummerWrapper.styleHoogte, 10)).to.equal(rnHoogteInitieel);
expect(parseInt(tekstvlak.invoer.styleHoogte, 10)).to.equal(tvHoogteInitieel);
});
});
import {Tekstvlak} from "bestelapp/aurelia/componenten/form/tekstvlak";
import {TekstvlakUiPart} from "bestelapp/component/aurelia/componenten/form/tekstvlak.part";
import {ComponentTest} from "bestelapp/component/helpers/componenttest";
export class TekstvlakTest extends ComponentTest<Tekstvlak, TekstvlakUiPart> {
protected getComponentnaam() { return "tekstvlak"; }
protected getPartClass() { return TekstvlakUiPart; }
protected getComponentPad() {
return "componenten/form/tekstvlak";
}
invoer = "";
readonly placeholder = "Dit is een placeholder";
readonly ariaLabelledby = "een-id";
setUp() {
super.setUp();
this.component
.inView('<tekstvlak invoer.bind="invoer" placeholder.bind="placeholder"'
+ 'aria-labelledby.bind="ariaLabelledby"></tekstvlak>')
.boundTo(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment