Skip to content

Commit

Permalink
Implement ControlValueAccessor and add proper demo page (0.1.0-beta.0)
Browse files Browse the repository at this point in the history
  • Loading branch information
leNicDev committed Oct 4, 2018
1 parent 0a719d8 commit fd88b36
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 30 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 0.1.0-beta.0 (2018-10-04)

## Features
- Implement ControlValueAccessor (allows data binding through ngModel and basic validation)
- Add proper demo page

## Fixes
- Fix issues with multiple hCaptcha instances

# 0.0.1 (2018-10-01)
Initial release
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ng-hcaptcha-app",
"version": "0.0.1",
"version": "0.1.0-beta.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
5 changes: 3 additions & 2 deletions projects/ng-hcaptcha/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ng-hcaptcha",
"version": "0.0.1",
"version": "0.1.0.beta.0",
"description": "hCaptcha Component for Angular 6+",
"keywords": [
"hcaptcha",
Expand All @@ -21,6 +21,7 @@
"license": "MIT",
"peerDependencies": {
"@angular/common": "^6.0.0-rc.0 || ^6.0.0",
"@angular/core": "^6.0.0-rc.0 || ^6.0.0"
"@angular/core": "^6.0.0-rc.0 || ^6.0.0",
"@angular/forms": "^6.0.0-rc.0 || ^6.0.0"
}
}
4 changes: 2 additions & 2 deletions projects/ng-hcaptcha/src/lib/ng-hcaptcha-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InjectionToken } from "@angular/core";
import { InjectionToken } from '@angular/core';

export interface CaptchaConfig {
/**
Expand All @@ -8,4 +8,4 @@ export interface CaptchaConfig {
siteKey?: string;
}

export const CAPTCHA_CONFIG = new InjectionToken<CaptchaConfig>('CAPTCHA_CONFIG');
export const CAPTCHA_CONFIG = new InjectionToken<CaptchaConfig>('CAPTCHA_CONFIG');
85 changes: 78 additions & 7 deletions projects/ng-hcaptcha/src/lib/ng-hcaptcha.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, Input, ViewChild, ElementRef, OnInit, Inject, NgZone, Output, EventEmitter } from '@angular/core';
import { Component, Input, ViewChild, ElementRef, OnInit, Inject, NgZone, Output, EventEmitter, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { CAPTCHA_CONFIG, CaptchaConfig } from './ng-hcaptcha-config';
import { Observable, Subscriber } from 'rxjs';

Expand All @@ -7,9 +8,16 @@ declare const window: any;
@Component({
selector: 'ng-hcaptcha',
template: '<div #captcha class="h-captcha"></div>',
styles: []
styles: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgHcaptchaComponent),
multi: true
}
]
})
export class NgHcaptchaComponent implements OnInit {
export class NgHcaptchaComponent implements OnInit, ControlValueAccessor {

@Input() siteKey: string;
@Input() theme: string;
Expand All @@ -22,13 +30,21 @@ export class NgHcaptchaComponent implements OnInit {
@Output() expired: EventEmitter<any> = new EventEmitter<any>();
@Output() error: EventEmitter<any> = new EventEmitter<any>();

private _value: string;
private widgetId: string;

onChange: any = () => {};
onTouched: any = () => {};


constructor(
@Inject(CAPTCHA_CONFIG) private config: CaptchaConfig,
private zone: NgZone
) {}


// Initialization

ngOnInit() {
// Load the hCaptcha script
this.loadHcaptcha().subscribe(
Expand All @@ -39,13 +55,16 @@ export class NgHcaptchaComponent implements OnInit {
theme: this.theme,
size: this.size,
tabindex: this.tabIndex,
callback: (res) => { this.zone.run(() => this.onVerify(res)) },
'expired-callback': (res) => { this.zone.run(() => this.onExpired(res)) },
'error-callback': (err) => { this.zone.run(() => this.onError(err)) }
callback: (res) => { this.zone.run(() => this.onVerify(res)); },
'expired-callback': (res) => { this.zone.run(() => this.onExpired(res)); },
'error-callback': (err) => { this.zone.run(() => this.onError(err)); }
};

// Render hCaptcha using the defined options
window.hcaptcha.render(this.captcha.nativeElement, options);

// Get widget ID
this.widgetId = this.findWidgetId();
},
(error) => {
console.error('Failed to load hCaptcha script', error);
Expand All @@ -54,11 +73,47 @@ export class NgHcaptchaComponent implements OnInit {
}


// ControlValueAccessor implementation

writeValue(value: string) {
// Needs to be implemented to make the FormGroup's reset function work
this.value = value;

// Reset hCaptcha.
// We need to check whether window.hcaptcha is defined because
// writeValue(value: any) can be called before hCaptcha has been intialized.
if (!this.value && window.hcaptcha) {
window.hcaptcha.reset(this.widgetId);
}
}

registerOnChange(fn: any) {
this.onChange = fn;
}

registerOnTouched(fn: any) {
this.onTouched = fn;
}

get value() {
return this._value;
}

set value(value: string) {
this._value = value;
this.onChange(value);
this.onTouched();
}


// Internal functions

/**
* Is called when the verification was successful
* @param response The verification token
*/
private onVerify(response: string) {
this.value = response;
this.verify.emit(response);
}

Expand Down Expand Up @@ -100,9 +155,25 @@ export class NgHcaptchaComponent implements OnInit {
script.onload = () => {
observer.next();
observer.complete();
}
};
document.head.appendChild(script);
});
}

/**
* Find the widget ID of the hCaptcha container.
*/
private findWidgetId(): string {
const children = this.captcha.nativeElement.children;

for (let i = 0; i < children.length; i++) {
// Found correct children when the hcaptchaWidgetId dataset property is set
if (children[i] && children[i].dataset && children[i].dataset.hcaptchaWidgetId) {
return children[i].dataset.hcaptchaWidgetId;
}
}

return null;
}

}
2 changes: 1 addition & 1 deletion projects/ng-hcaptcha/src/lib/ng-hcaptcha.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { CAPTCHA_CONFIG, CaptchaConfig } from './ng-hcaptcha-config';
exports: [NgHcaptchaComponent]
})
export class NgHcaptchaModule {

static forRoot(config?: CaptchaConfig): ModuleWithProviders {
const providers = config ? [{ provide: CAPTCHA_CONFIG, useValue: config }] : [];

Expand Down
41 changes: 33 additions & 8 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
<!--The content below is only a placeholder and can be replaced.-->
<div>
<h1>ng-hcaptcha Demo</h1>
</div>

<div class="hcaptcha-wrapper">
<ng-hcaptcha></ng-hcaptcha>
</div>
<header>
<div class="logo">
<span class="logo-prefix">ng-</span>
<img src="https://hcaptcha.com/static/img/site/horizontal-logo.svg">
<span class="logo-suffix">Demo</span>
</div>

<div class="app-version">
<span>v{{ appVersion }}</span>
</div>
</header>

<main>
<form class="demo-form" [formGroup]="signUpForm" (submit)="onSubmit()">
<div class="form-control">
<input type="email" placeholder="E-Mail" formControlName="email" [class.error]="!signUpForm.get('email').valid" required>
</div>

<div class="form-control">
<input type="password" placeholder="Password" formControlName="password" [class.error]="!signUpForm.get('password').valid" required>
</div>

<div class="form-control">
<input type="password" placeholder="Confirm password" formControlName="confirmPassword" [class.error]="!signUpForm.get('confirmPassword').valid" required>
</div>

<div class="flex justify-content-between">
<ng-hcaptcha formControlName="captcha"></ng-hcaptcha>

<button type="submit" class="btn primary signup" [disabled]="!signUpForm.valid">Sign up</button>
</div>
</form>
</main>
49 changes: 46 additions & 3 deletions src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
.hcaptcha-wrapper {
border: solid 1px #8d8d8d;
padding: 25px;
header {
width: 100%;
height: 80px;
margin-bottom: 50px;

.app-version {
position: absolute;
top: 10px;
right: 15px;
opacity: 0.5;
}
}

.logo {
height: 100%;
display: flex;
justify-content: center;
align-items: center;

span {
font-size: 3.5em;
font-weight: bold;
color: #4a4a4a;

}

.logo-prefix {
margin-right: 15px;
}

.logo-suffix {
margin-left: 15px;
}

img {
width: auto;
height: 100%;
}
}

main {
width: 100%;
}

.demo-form {
width: 30%;
margin: 0 auto;
}
22 changes: 22 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { environment } from '../environments/environment';

@Component({
selector: 'hc-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {

appVersion = environment.version;

signUpForm: FormGroup;


constructor(fb: FormBuilder) {
this.signUpForm = fb.group({
email: ['', Validators.compose([Validators.email, Validators.required])],
password: ['', Validators.required],
confirmPassword: ['', Validators.required],
captcha: ['', Validators.required]
});
}


onSubmit() {
this.signUpForm.reset();
}

}
8 changes: 6 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { NgHcaptchaModule } from 'ng-hcaptcha';
import { NgHcaptchaModule } from 'ng-hcaptcha';
import { environment } from '../environments/environment';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
NgHcaptchaModule.forRoot({
siteKey: 'YOUR_SITEKEY'
siteKey: environment.siteKey
})
],
providers: [],
Expand Down
4 changes: 3 additions & 1 deletion src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const environment = {
production: true
production: true,
version: require('../../package.json').version,
siteKey: '6de09d9c-8f26-4501-8141-49f4fa644d38'
};
4 changes: 3 additions & 1 deletion src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// The list of file replacements can be found in `angular.json`.

export const environment = {
production: false
production: false,
version: require('../../package.json').version,
siteKey: '6de09d9c-8f26-4501-8141-49f4fa644d38'
};

/*
Expand Down
Loading

0 comments on commit fd88b36

Please sign in to comment.