<!DOCTYPE html>
<html>
<head>
<base href="." />
<title>angular2 playground</title>
<link rel="stylesheet" href="style.css" />
<script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js/dist/zone.js"></script>
<script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
<script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import('app')
.catch(console.error.bind(console));
</script>
<link rel="stylesheet" href="https://unpkg.com/font-awesome@4.7.0/css/font-awesome.css">
<link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" integrity="sha384-CCTZv2q9I9m3UOxRLaJneXrrqKwUNOzZ6NGEUMwHtShDJ+nCoiXJCAgi05KfkLGY" crossorigin="anonymous">
</head>
<body>
<my-app>
loading...
</my-app>
</body>
</html>
/* Styles go here */
.error-message {
color:red;
font-size: .75em;
margin-left: 10rem;
}
/* it has to be block for Pure (flex by default) */
dorf-field-wrapper {
display: block !important;
}
### DORF - custom field
Example which shows the way of how to extend existing DORF set of fields.
https://github.com/mat3e/dorf
System.config({
//use typescript for compilation
transpiler: 'typescript',
//typescript compiler options
typescriptOptions: {
emitDecoratorMetadata: true
},
paths: {
'npm:': 'https://unpkg.com/'
},
//map tells the System loader where to look for things
map: {
'app': './src',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
'dorf': 'npm:dorf@3.0.0',
'rxjs': 'npm:rxjs',
'typescript': 'npm:typescript@2.0.2/lib/typescript.js'
},
//packages defines our app package
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
dorf: {
main: "./index.js",
defaultExtension: "js"
},
rxjs: {
defaultExtension: 'js'
}
}
});
//main entry point
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app';
platformBrowserDynamic().bootstrapModule(AppModule)
//our root app component
import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import { DorfModule } from "dorf";
import {
STAR_TAG,
StarRatingDefinition,
StarRatingMetadata,
StarRatingComponent
} from "./ext/star-rating.component";
import { Person } from './person/model';
import { PersonDetailComponent } from "./person/person-detail.component";
@Component({
selector: 'my-app',
template: `
<h2>Person in form: {{activeIndex >= 0 ? activePerson.fullname : "new person"}}</h2>
<person-details [domainObject]="activePerson" (createUpdate)=onCreateUpdate($event)></person-details>
<hr/>
<table class="pure-table pure-table-striped">
<tbody>
<tr *ngFor="let p of people; let i = index;">
<td>{{p.fullname}}</td>
<td><button (click)=onClick(i) class="pure-button">edit</button></td>
</tr>
</tbody>
</table>
`,
})
export class App {
activeIndex = -1;
activePerson = new Person();
people: Person[] = [];
onClick(index: number) {
this.activeIndex = index;
this.activePerson = this.people[index];
}
onCreateUpdate(person: IPerson) {
this.activeIndex >= 0 ? this.people[this.activeIndex] = new Person(person)
: this.people.push(new Person(person));
this.reset();
}
private reset() {
this.activeIndex = -1;
this.activePerson = new Person();
}
}
@NgModule({
imports: [BrowserModule, DorfModule.forRoot({
css: {
form: 'pure-form pure-form-aligned',
wrapper: 'pure-control-group',
error: 'error-message',
buttons: {
save: 'pure-button pure-button-primary',
reset: 'hidden',
group: 'pure-controls'
}
},
dorfFields: [STAR_TAG]
})],
declarations: [App, StarRatingComponent, PersonDetailComponent],
bootstrap: [App]
})
export class AppModule { }
.rating .fa.fa-star {
color: black;
}
.rating .fa.fa-star.active {
color: #DCAA00;
}
.rating:hover .fa.fa-star {
cursor: pointer;
color: #DCAA00;
}
.rating .fa.fa-star:active {
color: red;
}
.rating .fa.fa-star:hover~.fa.fa-star {
color: black;
}
import { Component } from "@angular/core";
import { FormControl } from "@angular/forms";
import {
DorfConfigService,
IDorfFieldDefinition,
IDorfFieldMetadata,
DorfFieldDefinition,
createPropertyDecorator,
DorfFieldMetadata,
AbstractDorfFieldComponent
} from "dorf";
/**
* First - extend a standard interface.
*/
export interface IStarRatingDefinition extends IDorfFieldDefinition<number> {
max: number;
}
/**
* Second - extend standard field definition and implement the above interface.
*/
export class StarRatingDefinition extends DorfFieldDefinition<number> implements IStarRatingDefinition {
static get TAG() {
return "star-rating";
}
private _max = 5;
constructor(options?: IStarRatingDefinition<T>) {
super(options);
if (options) {
this._max = options.max > 1 ? options.max : this._max;
}
}
get max() { return this._max; }
get tag() { return StarRatingDefinition.TAG; }
}
/**
* Optional - create a decorator.
*/
export function DorfStars(options: IStarRatingDefinition) {
return createPropertyDecorator<T>(new StarRatingDefinition(options));
}
/**
* Third - extend standard metadata and implement the above interface.
*/
export class StarRatingMetadata extends DorfFieldMetadata<number, StarRatingDefinition> implements IStarRatingDefinition {
constructor(definition = new StarRatingDefinition<T>(), options?: IDorfFieldMetadata<T>) {
super(definition, options);
}
get max() { return this.definition.max; }
}
/**
* Last but not least - create component which consumes the above metadata
* (extension of an abstract component + implementation of the above interface).
*/
@Component({
selector: StarRatingDefinition.TAG,
styleUrls: ["src/ext/star-rating.component.css"],
templateUrl: "src/ext/star-rating.component.html"
})
export class StarRatingComponent extends AbstractDorfFieldComponent<number, StarRatingMetadata> implements IStarRatingDefinition {
constructor(config: DorfConfigService) {
super(config);
}
setValue(val: number) {
// 1 more than array index
this.formControl.setValue(val + 1);
}
get max() { return this.metadata.max; }
get stars() { return new Array(this.max); }
get value() { return this.formControl.value; }
}
export const STAR_TAG: DorfTag<typeof StarRatingDefinition, typeof StarRatingMetadata> = {
tag: StarRatingDefinition.TAG,
definition: StarRatingDefinition,
metadata: StarRatingMetadata
}
<span class="rating">
<input type="hidden" [id]="key" [name]="key" [formControl]="formControl" />
<span *ngFor="let star of stars; let i=index;" class="fa fa-star" [ngClass]="{'active': (i < value)}" (click)="setValue(i)"></span>
</span>
import { Validators } from "@angular/forms";
import { DorfObject, DorfInput, DorfRadio, DorfSelect, DorfCheckbox } from "dorf";
import { DorfStars } from "../ext/star-rating.component";
export interface IPerson {
name: string;
surname: string;
rating: number;
}
@DorfObject()
export class Person implements IPerson {
@DorfInput<string>({
label: "Name",
type: "text",
validator: Validators.required,
errorMessage: "Name is required"
})
name: string;
@DorfInput<string>({
label: "Surname",
type: "text",
validator: Validators.required,
errorMessage: "Surname is required"
})
surname: string;
@DorfStars({
label: "Rating",
max: 7
})
rating: number;
get fullname() {
return this.name + " " + this.surname;
}
constructor(base: IPerson = null) {
if (base) {
this.name = base.name;
this.surname = base.surname;
this.rating = base.rating;
}
}
}
import { Component, Output, EventEmitter } from '@angular/core';
import { PropertiesToDorfDefinitionsMap, IDorfForm, DorfForm, DorfObjectInput, DorfConfigService } from 'dorf';
import { IPerson, Person } from './model';
import { STAR_TAG } from '../ext/star-rating.component';
/**
* Lightweight class which creates a form.
* Template provided from the library, but it is possible to prowide a custom one (which uses Dorf components).
*
* It's important to pass config in the constructor and define onDorfSubmit method.
*/
@DorfForm({
additionalTags: [STAR_TAG]
})
@Component({
selector: 'person-details'
})
export class PersonDetailComponent implements IDorfForm {
@DorfObjectInput() domainObject: Person;
@Output() createUpdate = new EventEmitter<IPerson>();
constructor(public config: DorfConfigService) { }
onDorfSubmit() {
let result = this['form'].value as IPerson;
console.log(result);
this.createUpdate.emit(result);
}
}