@steinv/filippine
Version:
Filippine - acrostic word puzzle with Angular
252 lines (243 loc) • 10.5 kB
JavaScript
import { EventEmitter, Component, ChangeDetectionStrategy, ChangeDetectorRef, Input, Output, ElementRef, HostBinding, ContentChildren, Directive, HostListener, NgModule } from '@angular/core';
import { FormGroup, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
class FilippineComponent {
constructor(_changeDetectorRef) {
this._changeDetectorRef = _changeDetectorRef;
this.answer = new EventEmitter();
/**
* Emits when the form has been completed
* returns false when answered wrongly, true when correctly answered
*/
this.completed = new EventEmitter();
this.filippineForm = new FormGroup({});
this.columns = 1;
this.tiles = [];
}
ngOnInit() {
/**
* Calculate how big the grid should be
*/
const left = this.configuration.questions.reduce((acc, q) => acc = acc > q.answerPosition ? acc : q.answerPosition, 0);
const right = this.configuration.questions.reduce((acc, q) => acc = acc > q.answerLength - q.answerPosition ? acc : q.answerLength - q.answerPosition, 0);
this.columns = left + right;
/**
* map the configuration to the tiles on the grid
* Creates a filippineForm that can validate each question
*/
let inputFieldCounter = 0;
this.configuration.questions.map((m, i) => {
const blankCellsLeft = left - m.answerPosition;
const blankCellsRight = this.columns - (m.answerLength + blankCellsLeft);
const inputCells = m.answerLength;
this.tiles.push(new Array());
if (blankCellsLeft > 0) {
this.tiles[i].push({ colspan: blankCellsLeft, question: false, highlight: false });
}
const formQuestion = new FormGroup({}, this.rightAnswer(m));
for (let index = 0; index < inputCells; index++) {
this.tiles[i].push({
index: inputFieldCounter,
highlight: (index === m.answerPosition),
colspan: 1,
question: true,
name: index,
coordinates: i.toString() + ',' + (index + blankCellsLeft).toString() // coorindates row, column
});
inputFieldCounter++;
formQuestion.addControl(index.toString(), new FormControl('')); // m.answer.charAt(index)
}
this.filippineForm.addControl(i.toString(), formQuestion);
if (blankCellsRight > 0) {
this.tiles[i].push({ colspan: blankCellsRight, question: false, highlight: false });
}
this.filippineForm.valueChanges.subscribe(() => {
for (const group in this.filippineForm.controls) {
const controlGroup = this.filippineForm.get(group);
for (const field in controlGroup.controls) {
const inputField = controlGroup.get(field);
if (!inputField || !inputField.value) {
return;
}
}
}
this.completed.emit(this.filippineForm.valid);
});
});
}
rightAnswer(question) {
return (c) => {
let formGroup = c;
let invalid = false;
let reply = '';
for (const field in formGroup.controls) {
const control = formGroup.get(field);
// only validate answer when entire group is filled
if (!control || !control.value) {
return null;
}
// concat control value upon reply string
reply += control.value;
// validate response
if (question.answer &&
question.answer.length >= +field &&
control.value.toUpperCase() !== question.answer.charAt(+field).toUpperCase()) {
invalid = true;
}
}
// emit answer that client entered
if (reply) {
this.answer.emit({
question: question,
answer: reply,
});
}
return invalid ? { invalid: true } : null;
};
}
ngAfterViewInit() {
this._changeDetectorRef.detectChanges();
}
}
FilippineComponent.decorators = [
{ type: Component, args: [{
selector: 'filippine',
template: "\n<form [formGroup]=\"filippineForm\">\n <grid-list \n *ngFor=\"let row of tiles; let i = index\" \n [cols]=\"columns\" \n gutterSize=\"0\"\n [formGroupName]=\"i\"\n >\n <grid-tile \n *ngFor=\"let tile of row\"\n [colspan]=\"tile.colspan\"\n [ngClass]=\"{'input': tile.question, 'spacer': !tile.question, '': true, 'highlight': tile.highlight}\">\n <input \n *ngIf=\"tile.question === true\" \n AutoTab \n [id]=\"tile.coordinates\" \n [name]=\"tile.index\" \n type=\"text\" \n maxlength=\"1\" \n [formControlName]=\"tile.name\" \n />\n </grid-tile>\n </grid-list>\n</form>",
changeDetection: ChangeDetectionStrategy.OnPush,
styles: ["grid-tile{border:1px solid #000;box-shadow:0 0 0 1px #000;display:flex;position:relative}grid-tile input{background:transparent;border:none;height:0;margin:0;outline:none;padding:50% 0;text-align:center;text-transform:uppercase;width:100%}.spacer{background-color:#000}.input{background-color:#fff;border:1px solid #000;box-shadow:0 0 0 1px #000}.highlight{background-color:#ff0}grid-list.ng-invalid .input{border-color:red;box-shadow:0 0 0 1px red;z-index:1}"]
},] }
];
FilippineComponent.ctorParameters = () => [
{ type: ChangeDetectorRef }
];
FilippineComponent.propDecorators = {
configuration: [{ type: Input }],
answer: [{ type: Output }],
completed: [{ type: Output }]
};
class GridTileComponent {
constructor(_elementRef) {
this._elementRef = _elementRef;
this.colspan = 1;
this._width = '100%';
}
set width(width) {
this._width = width;
}
ngAfterContentInit() {
const element = this._elementRef.nativeElement;
}
}
GridTileComponent.decorators = [
{ type: Component, args: [{
selector: 'grid-tile',
template: "<ng-content></ng-content>",
changeDetection: ChangeDetectionStrategy.OnPush,
styles: [":host{box-sizing:border-box}"]
},] }
];
GridTileComponent.ctorParameters = () => [
{ type: ElementRef }
];
GridTileComponent.propDecorators = {
colspan: [{ type: Input }],
_width: [{ type: HostBinding, args: ['style.width',] }]
};
class GridListComponent {
constructor() { }
ngAfterContentInit() {
this.tiles.map(tile => { tile.width = ((tile.colspan * 100) / this.cols).toString() + '%'; });
}
}
GridListComponent.decorators = [
{ type: Component, args: [{
selector: 'grid-list',
template: "<ng-content [select]=\"tiles\"></ng-content>\n",
changeDetection: ChangeDetectionStrategy.OnPush,
styles: [":host{display:flex;width:100%}"]
},] }
];
GridListComponent.ctorParameters = () => [];
GridListComponent.propDecorators = {
cols: [{ type: Input }],
tiles: [{ type: ContentChildren, args: [GridTileComponent,] }]
};
class AutoTabDirective {
constructor() { }
onKeyUp(event) {
const input = event.target;
if (!input.id || event.key === 'Tab' || event.key === 'Shift') {
return;
}
const length = input.value.length;
const index = +input.name;
const position = input.id.split(',');
let elementByCoordinates = position;
let elementByName = index;
if ((length === 1 || event.key === 'ArrowRight') && input.id) {
elementByCoordinates = position[0] + ',' + (+position[1] + 1);
elementByName = index + 1;
}
if ((event.key === 'Backspace' || event.key === 'ArrowLeft') && input.id) {
elementByCoordinates = position[0] + ',' + (+position[1] - 1);
elementByName = index - 1;
}
if ((event.key === 'ArrowUp') && input.id) {
elementByCoordinates = (+position[0] - 1) + ',' + position[1];
elementByName = index - 1;
}
if ((event.key === 'ArrowDown') && input.id) {
elementByCoordinates = (+position[0] + 1) + ',' + position[1];
elementByName = index + 1;
}
const field = document.getElementById(elementByCoordinates);
if (field) {
field.focus();
field.setSelectionRange(0, 1);
return;
}
else {
const fields = document.getElementsByName(elementByName.toString());
if (fields && fields.item(0)) {
const inputField = fields.item(0);
inputField.focus();
inputField.setSelectionRange(0, 1);
}
}
}
}
AutoTabDirective.decorators = [
{ type: Directive, args: [{
selector: '[AutoTab]',
},] }
];
AutoTabDirective.ctorParameters = () => [];
AutoTabDirective.propDecorators = {
onKeyUp: [{ type: HostListener, args: ['keyup', ['$event'],] }]
};
class FilippineModule {
}
FilippineModule.decorators = [
{ type: NgModule, args: [{
declarations: [
AutoTabDirective,
FilippineComponent,
GridListComponent,
GridTileComponent
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
],
exports: [FilippineComponent]
},] }
];
/*
* Public API Surface of filippine
*/
/**
* Generated bundle index. Do not edit.
*/
export { FilippineComponent, FilippineModule, AutoTabDirective as ɵa, GridListComponent as ɵb, GridTileComponent as ɵc };
//# sourceMappingURL=steinv-filippine.js.map