Skip to content

Commit

Permalink
feat(form): map field (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbourget authored and mbarbeau committed May 8, 2017
1 parent 2f7a896 commit 7d1bf4b
Show file tree
Hide file tree
Showing 17 changed files with 322 additions and 8 deletions.
31 changes: 31 additions & 0 deletions src/demo-app/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,35 @@
</md-card-content>
</md-card>

<md-card>
<md-card-subtitle>Form module</md-card-subtitle>
<md-card-title>map-field.component</md-card-title>
<md-card-content>
<form [formGroup]="demoForm">

<div class="igo-map-field-container">
<igo-map-field
formControlName="location"
[placeholder]="'Location' | translate"
[decimals]="8"
[readonly]="false"
[layers]="map.layers$ | async"
[view]="contextService.context$.value ? (contextService.context$ | async).map.view : undefined">
</igo-map-field>
</div>

<div class="igo-form-button-group">
<button
md-raised-button
type="button"
[disabled]="!demoForm.valid"
(click)="handleFormSubmit(demoForm.value, demoForm.valid)">
{{'Submit' | translate}}
</button>
</div>

</form>
</md-card-content>
</md-card>

</md-sidenav-container>
7 changes: 6 additions & 1 deletion src/demo-app/app/app.component.styl
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ md-sidenav {
md-sidenav p {
width: 200px;
padding: 20px;
}
}

.igo-map-field-container {
width: 300px;
height: 200px;
}
20 changes: 19 additions & 1 deletion src/demo-app/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';

import { ContextService, Feature, FeatureService, IgoMap,
LanguageService, MessageService, OverlayService,
Expand All @@ -13,13 +14,19 @@ export class AppComponent implements OnInit {

public map = new IgoMap();
public searchTerm: string;
public demoForm: FormGroup;

get locationField () {
return (<FormControl>this.demoForm.controls['location']);
}

constructor(public contextService: ContextService,
public featureService: FeatureService,
public messageService: MessageService,
public overlayService: OverlayService,
public toolService: ToolService,
public language: LanguageService) {}
public language: LanguageService,
private formBuilder: FormBuilder) {}

ngOnInit() {
// If you do not want to load a context from a file,
Expand All @@ -28,6 +35,12 @@ export class AppComponent implements OnInit {
// as the contexts in ../contexts/

this.contextService.loadContext('_default');

this.demoForm = this.formBuilder.group({
location: ['', [
Validators.required
]]
});
}

handleSearch(term: string) {
Expand All @@ -50,4 +63,9 @@ export class AppComponent implements OnInit {
this.featureService.unfocusFeature();
this.overlayService.clear();
}

handleFormSubmit(data: any, isValid: boolean) {
console.log(data);
console.log(isValid);
}
}
4 changes: 3 additions & 1 deletion src/demo-app/assets/locale/en.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"Search for an address or a place": "Search for an address or a place"
"Location": "Location",
"Search for an address or a place": "Search for an address or a place",
"Submit": "Submit"
}
4 changes: 3 additions & 1 deletion src/demo-app/assets/locale/fr.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"Search for an address or a place": "Rechercher une adresse, un lieu ou une couche"
"Location": "Localisation",
"Search for an address or a place": "Rechercher une adresse, un lieu ou une couche",
"Submit": "Soumettre"
}
1 change: 1 addition & 0 deletions src/lib/form/fields/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './map-field';
1 change: 1 addition & 0 deletions src/lib/form/fields/map-field/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './map-field.component';
11 changes: 11 additions & 0 deletions src/lib/form/fields/map-field/map-field.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div>
<md-input-container>
<input
mdInput
[placeholder]="placeholder"
[readonly]="readonly"
[ngModel]="value"
(ngModelChange)="handleValueChange($event)">
</md-input-container>
<igo-map-browser [map]="map" [view]="view"></igo-map-browser>
</div>
34 changes: 34 additions & 0 deletions src/lib/form/fields/map-field/map-field.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { IgoSharedModule } from '../../../shared';
import { MapBrowserComponent } from '../../../map';

import { MapFieldComponent } from './map-field.component';

describe('MapFieldComponent', () => {
let component: MapFieldComponent;
let fixture: ComponentFixture<MapFieldComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
IgoSharedModule,
],
declarations: [
MapBrowserComponent,
MapFieldComponent
]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(MapFieldComponent);
component = fixture.componentInstance;
});

it('should create', () => {
expect(component).toBeTruthy();
});

});
15 changes: 15 additions & 0 deletions src/lib/form/fields/map-field/map-field.component.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@require '../../../../style/var.styl';

:host {
position: relative;
display: block;
}

:host, .igo-map-browser-target {
width: 100%;
height: 100%;
}

input {
width: 100%
}
153 changes: 153 additions & 0 deletions src/lib/form/fields/map-field/map-field.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { Component, Input, forwardRef,
AfterViewInit, OnDestroy } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

import { IgoMap, MapViewOptions } from '../../../map';
import { Layer } from '../../../layer';


@Component({
selector: 'igo-map-field',
templateUrl: './map-field.component.html',
styleUrls: ['./map-field.component.styl'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MapFieldComponent),
multi: true
}
]
})
export class MapFieldComponent
implements AfterViewInit, OnDestroy, ControlValueAccessor {

@Input()
get placeholder() { return this._placeholder; }
set placeholder(value: string) {
this._placeholder = value;
}
private _placeholder: string;

@Input()
get decimals(): number { return this._decimals; }
set decimals(value: number) {
this._decimals = value;
}
private _decimals: number = 6;

@Input()
get readonly(): boolean { return this._readonly; }
set readonly(value: boolean) {
this._readonly = value;
}
protected _readonly: boolean = false;

@Input()
get value(): [number, number] { return this._value; }
set value(value: [number, number]) {
this.map.clearOverlay();

if (value === undefined) {
this._value = value;
this.onChange(this._value);
return;
}

if (!isNaN(+value[0]) && !isNaN(+value[1])) {
const values = [+value[0], +value[1]] as [number, number];

this.addOverlay(values);

this._value = [
+values[0].toFixed(this.decimals),
+values[1].toFixed(this.decimals)
];

this.onChange(this._value);
this.onTouched();
}
}
private _value: [number, number];

@Input()
get view(): MapViewOptions { return this._view; }
set view(value: MapViewOptions) {
this._view = value;
if (this.map !== undefined) {
this.map.setView(value);
}
}
protected _view: MapViewOptions;

@Input()
get layers(): Layer[] { return this._layers; }
set layers(value: Layer[]) {
this._layers = value;
this.map.removeLayers();
this.map.addLayers(value);
}
private _layers: Layer[];

public map = new IgoMap();
public projection = 'EPSG:4326';

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

constructor() {}

ngAfterViewInit() {
this.map.olMap.on('singleclick', this.handleMapClick, this);
}

ngOnDestroy() {
this.map.olMap.un('singleclick', this.handleMapClick, this);
}

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

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

writeValue(value: [number, number]) {
if (value) {
this.value = value;
}
}

handleValueChange(value: string) {
this.value = this.parseValue(value);
}

private handleMapClick(event: ol.MapBrowserEvent) {
this.value = ol.proj.transform(
event.coordinate, this.map.projection, this.projection);
}

private parseValue(value: string): [number, number] | undefined {
if (value === undefined || value === '') {
return undefined;
}

const values = value.split(',').filter(v => v !== '');
if (values.length === 2) {
return [+values[0], +values[1]];
}

return undefined;
}

private addOverlay(coordinates: [number, number]) {
const geometry = new ol.geom.Point(
ol.proj.transform(coordinates, this.projection, this.map.projection));
const extent = geometry.getExtent();
const feature = new ol.Feature({geometry: geometry});

this.map.moveToExtent(extent);
this.map.addOverlay(feature);
}

}
1 change: 1 addition & 0 deletions src/lib/form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './module';
30 changes: 30 additions & 0 deletions src/lib/form/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NgModule, ModuleWithProviders } from '@angular/core';

import { IgoSharedModule } from '../shared';
import { IgoMapModule } from '../map';

import { MapFieldComponent } from './fields/map-field';


@NgModule({
imports: [
IgoSharedModule,
IgoMapModule
],
exports: [
MapFieldComponent
],
declarations: [
MapFieldComponent
]
})
export class IgoFormModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: IgoFormModule,
providers: []
};
}
}

export * from './fields';
10 changes: 6 additions & 4 deletions src/lib/map/map-browser/map-browser.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,19 @@ export class MapBrowserComponent implements AfterViewInit {
set map(value: IgoMap) {
this._map = value;
}
private _map: IgoMap;
protected _map: IgoMap;

@Input()
get view(): MapViewOptions { return this._view; }
set view(value: MapViewOptions) {
this._view = value;
this.map.setView(value);
if (this.map !== undefined) {
this.map.setView(value);
}
}
private _view: MapViewOptions;
protected _view: MapViewOptions;

public id: string = 'igo-map-target';
public id: string = `igo-map-target-${new Date().getTime()}`;

constructor() {}

Expand Down
Loading

0 comments on commit 7d1bf4b

Please sign in to comment.