UNPKG

@steinv/filippine

Version:

Filippine - acrostic word puzzle with Angular

252 lines (243 loc) 10.5 kB
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