Skip to content

Commit e84482d

Browse files
author
Mirek Simek
committed
material upgrade to 5.2.2
implemented paginated foreign key results, sample demo with paginated table, docs
1 parent 5d40744 commit e84482d

File tree

12 files changed

+261
-34
lines changed

12 files changed

+261
-34
lines changed

README.rst

+5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ See demos at http://mesemus.no-ip.org:12569
4949

5050
.. image:: https://raw.githubusercontent.com/mesemus/django-angular-dynamic-forms/develop/docs/demo.png
5151

52+
With a bit of work on your side, foreign keys are supported as well (see the demos for details)
53+
54+
.. image:: https://raw.githubusercontent.com/mesemus/django-angular-dynamic-forms/develop/docs/foreign_key.png
55+
56+
5257

5358
Installation
5459
------------

angular/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"@angular/core": "^5.2.0",
4040
"@angular/flex-layout": "^2.0.0-beta.12",
4141
"@angular/forms": "^5.2.0",
42-
"@angular/material": "^5.1.1",
42+
"@angular/material": "^5.2.2",
4343
"@ng-dynamic-forms/core": "^5.4.4",
4444
"@ng-dynamic-forms/ui-material": "^5.4.4",
4545
"core-js": "^2.4.1",

angular/src/foreign.ts

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ export interface ForeignFieldLookupConfig {
3636
* true if multiple values are allowed, false otherwise
3737
*/
3838
multiple: boolean;
39+
40+
/**
41+
* autocomplete url if set by the backend
42+
*/
43+
autocompleteUrl?: string;
3944
}
4045

4146
export interface ForeignFieldLookupComponentData {

angular/src/impl/django-form-content.component.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export class DjangoFormContentComponent implements OnInit, OnDestroy {
239239
const native = valueAccessor._elementRef.nativeElement;
240240
// bind mousedown on the wrapper so that label is also active
241241
native.parentElement.addEventListener('mousedown', (event) => {
242-
this._runForeignKeySelection(name, def, formModel, native);
242+
this._runForeignKeySelection(name, def, formModel);
243243
});
244244
}
245245
}
@@ -248,6 +248,7 @@ export class DjangoFormContentComponent implements OnInit, OnDestroy {
248248
this.zone.run(() => {
249249

250250
let component: Type<ForeignFieldLookupComponent>;
251+
console.log('factory is', this.foreignFieldLookupFactory);
251252
if (this.foreignFieldLookupFactory) {
252253
component = this.foreignFieldLookupFactory.getComponent(def);
253254
}
@@ -282,7 +283,7 @@ export class DjangoFormContentComponent implements OnInit, OnDestroy {
282283
if (!result.length) {
283284
// deselect value
284285
formModel.options = [];
285-
formModel.select();
286+
// formModel.select();
286287
return;
287288
}
288289
formModel.options = result.map((r) => ({

angular_dynamic_forms/rest.py

+28-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from django.core.exceptions import FieldDoesNotExist
77
from django.db.models import TextField
8-
from django.http import HttpResponseNotFound
8+
from django.http import HttpResponseNotFound, HttpResponseNotAllowed
99
from django.template import Template, Context
1010
from django.utils.translation import gettext
1111
from rest_framework import renderers
@@ -426,9 +426,10 @@ class ForeignFieldAutoCompleteMixin(object):
426426
max_returned_items = 10
427427

428428
class __AutoCompleteRec:
429-
def __init__(self, search_method, serializer):
429+
def __init__(self, search_method, serializer, pagination):
430430
self.search_method = search_method
431431
self.serializer = serializer
432+
self.pagination = pagination
432433

433434
# noinspection PyUnusedLocal
434435
@detail_route(renderer_classes=[renderers.JSONRenderer], url_path='foreign-autocomplete/(?P<autocomplete_id>.*)',
@@ -453,7 +454,8 @@ def _foreign_autocomplete_definitions(self):
453454
continue
454455
# noinspection PyUnresolvedReferences,PyProtectedMember
455456
ret[method._foreign_autocomplete_field] = \
456-
ForeignFieldAutoCompleteMixin.__AutoCompleteRec(method, method._foreign_autocomplete_serializer)
457+
ForeignFieldAutoCompleteMixin.__AutoCompleteRec(method, method._foreign_autocomplete_serializer,
458+
method._foreign_autocomplete_pagination)
457459
self._foreign_autocomplete_definitions_cache = ret
458460
return ret
459461

@@ -480,15 +482,35 @@ def _foreign_autocomplete(self, request, has_instance, **kwargs):
480482
if item_id not in foreign_autocomplete_definitions:
481483
return HttpResponseNotFound()
482484
filter_method = foreign_autocomplete_definitions[item_id].search_method
483-
qs = filter_method(request)[:self.max_returned_items]
485+
qs = filter_method(request)
486+
paginated = foreign_autocomplete_definitions[item_id].pagination
487+
total = 0
488+
if paginated:
489+
pageIndex = int(request.GET.get('pageIndex', 0))
490+
pageSize = int(request.GET.get('pageSize', 0))
491+
if pageSize > self.max_returned_items:
492+
return HttpResponseNotAllowed('pageSize too big')
493+
total = qs.count()
494+
if pageSize:
495+
qs = qs[pageIndex * pageSize : (pageIndex + 1) * pageSize]
496+
else:
497+
qs = qs[:self.max_returned_items]
498+
484499
serializer = foreign_autocomplete_definitions[item_id].serializer(many=True, instance=qs)
485-
return Response(serializer.data)
500+
if paginated:
501+
return Response({
502+
'length' : total,
503+
'items' : serializer.data
504+
})
505+
else:
506+
return Response(serializer.data)
486507

487508

488-
def foreign_field_autocomplete(field, serializer):
509+
def foreign_field_autocomplete(field, serializer, pagination=False):
489510
def wrapper(real_func):
490511
real_func._foreign_autocomplete_field = field
491512
real_func._foreign_autocomplete_serializer = serializer
513+
real_func._foreign_autocomplete_pagination = pagination
492514
return real_func
493515

494516
return wrapper

demo/angular/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
"private": true,
1515
"dependencies": {
1616
"@angular/animations": "^5.2.0",
17-
"@angular/cdk": "^5.2.1",
17+
"@angular/cdk": "^5.2.2",
1818
"@angular/common": "^5.2.0",
1919
"@angular/compiler": "^5.2.0",
2020
"@angular/core": "^5.2.0",
2121
"@angular/flex-layout": "2.0.0-beta.12",
2222
"@angular/forms": "^5.2.0",
2323
"@angular/http": "^5.2.0",
24-
"@angular/material": "^5.2.1",
24+
"@angular/material": "^5.2.2",
2525
"@angular/platform-browser": "^5.2.0",
2626
"@angular/platform-browser-dynamic": "^5.2.0",
2727
"@angular/router": "^5.2.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {Injectable, Type} from '@angular/core';
2+
import {ForeignFieldLookupFactory, ForeignFieldLookupComponent} from 'django-angular-dynamic-forms/foreign';
3+
import {ForeignSelectorComponent} from './foreign-selector.component';
4+
5+
@Injectable()
6+
export class ForeignSelectorFactoryService implements ForeignFieldLookupFactory {
7+
8+
constructor() {
9+
}
10+
11+
public getComponent(lookupConfig: any): Type<ForeignFieldLookupComponent> {
12+
if (lookupConfig.id === 'foreign_key') {
13+
return ForeignSelectorComponent;
14+
}
15+
return undefined;
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import {AfterContentInit, AfterViewInit, Component, Inject, OnDestroy, OnInit, ViewChild} from '@angular/core';
2+
import {
3+
ForeignFieldLookupComponent, ForeignFieldLookupConfig, ForeignFieldLookupResult,
4+
ForeignFieldLookupComponentData
5+
} from 'django-angular-dynamic-forms';
6+
import {
7+
MAT_DIALOG_DATA, MatDialogRef, MatPaginator, MatSort, MatTableDataSource, PageEvent,
8+
Sort
9+
} from '@angular/material';
10+
import {FormBuilder, FormGroup} from '@angular/forms';
11+
import {Observable} from 'rxjs/Observable';
12+
import {debounceTime, distinctUntilChanged, mergeMap, tap} from 'rxjs/operators';
13+
import {map} from 'rxjs/operators';
14+
import 'rxjs/observable/merge';
15+
import {of as observableOf} from 'rxjs/observable/of';
16+
import {combineLatest} from 'rxjs/observable/combineLatest';
17+
import {HttpClient} from '@angular/common/http';
18+
import {Subscription} from 'rxjs/Subscription';
19+
20+
@Component({
21+
selector: 'app-foreign-selector',
22+
template:
23+
`Write a few letters of the city name and then select the city.
24+
You can also
25+
<button mat-button (click)="clear()">clear</button> the previous value:
26+
27+
<mat-table [dataSource]="dataSource" matSort>
28+
29+
<ng-container matColumnDef="name">
30+
<mat-header-cell *matHeaderCellDef mat-sort-header>
31+
<form [formGroup]="form">
32+
<mat-input-container>
33+
<input formControlName="query" matInput placeholder="City name filter">
34+
</mat-input-container>
35+
</form>
36+
</mat-header-cell>
37+
<mat-cell *matCellDef="let row"> {{row.name}}</mat-cell>
38+
</ng-container>
39+
40+
<ng-container matColumnDef="zipcode">
41+
<mat-header-cell *matHeaderCellDef mat-sort-header> ZIP Code</mat-header-cell>
42+
<mat-cell *matCellDef="let row"> {{row.zipcode}}</mat-cell>
43+
</ng-container>
44+
45+
<ng-container matColumnDef="comment">
46+
<mat-header-cell *matHeaderCellDef mat-sort-header> Comment</mat-header-cell>
47+
<mat-cell *matCellDef="let row"> {{row.comment}}</mat-cell>
48+
</ng-container>
49+
50+
<ng-container matColumnDef="action">
51+
<mat-header-cell *matHeaderCellDef mat-sort-header></mat-header-cell>
52+
<mat-cell *matCellDef="let row">
53+
<button mat-button (click)="select(row)">select this</button>
54+
</mat-cell>
55+
</ng-container>
56+
57+
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
58+
<mat-row *matRowDef="let row; columns: displayedColumns;">
59+
</mat-row>
60+
</mat-table>
61+
62+
<mat-paginator [pageSizeOptions]="[5, 10]" [length]="itemCount"></mat-paginator>
63+
`,
64+
styles: [
65+
`table {
66+
max-width: 300px;
67+
width: 100%;
68+
border-collapse: collapse;
69+
}`
70+
]
71+
})
72+
export class ForeignSelectorComponent implements OnInit, ForeignFieldLookupComponent, AfterViewInit, OnDestroy {
73+
public form: FormGroup;
74+
public displayedColumns = ['name', 'zipcode', 'comment', 'action'];
75+
public dataSource = new MatTableDataSource<any>([]);
76+
public itemCount = 0;
77+
78+
@ViewChild(MatPaginator) paginator: MatPaginator;
79+
@ViewChild(MatSort) sort: MatSort;
80+
private subscription: Subscription;
81+
82+
83+
constructor(public dialogRef: MatDialogRef<ForeignFieldLookupComponent>,
84+
@Inject(MAT_DIALOG_DATA) public data: ForeignFieldLookupComponentData,
85+
formBuilder: FormBuilder,
86+
private http: HttpClient) {
87+
88+
this.form = formBuilder.group({query: []});
89+
}
90+
91+
ngAfterViewInit(): void {
92+
93+
console.log(this.paginator, this.sort);
94+
95+
const filter$ = Observable.merge(
96+
this.form.valueChanges.pipe(
97+
map((val) => val.query),
98+
debounceTime(500),
99+
),
100+
observableOf('')
101+
);
102+
103+
const paginator$ = Observable.merge(
104+
this.paginator.page,
105+
// default
106+
observableOf({
107+
pageIndex: 0,
108+
pageSize: 5
109+
})) as Observable<PageEvent>;
110+
111+
const sort$ = Observable.merge(
112+
this.sort.sortChange,
113+
// default
114+
observableOf({
115+
active: '',
116+
direction: ''
117+
})) as Observable<Sort>;
118+
119+
this.subscription = combineLatest(filter$, paginator$, sort$).pipe(
120+
distinctUntilChanged(),
121+
map((x) => ({
122+
filter: x[0],
123+
paginator: x[1],
124+
sort: x[2]
125+
})),
126+
// normally you would use a service here ...
127+
mergeMap((opts) => this.http.get<any>(this.data.config.autocompleteUrl, {
128+
params: {
129+
query: opts.filter,
130+
pageIndex: opts.paginator.pageIndex.toString(),
131+
pageSize: opts.paginator.pageSize.toString(),
132+
sortBy: opts.sort.active,
133+
sortDirection: opts.sort.direction
134+
}
135+
})
136+
)
137+
).subscribe((cities) => {
138+
this.dataSource = new MatTableDataSource<any>(cities.items);
139+
this.itemCount = cities.length;
140+
});
141+
}
142+
143+
ngOnInit() {
144+
}
145+
146+
ngOnDestroy(): void {
147+
this.subscription.unsubscribe();
148+
}
149+
150+
select(city: any) {
151+
const result: ForeignFieldLookupResult[] = [
152+
{
153+
formatted_value: city.name,
154+
key: city.id
155+
}
156+
];
157+
this.dialogRef.close(result);
158+
}
159+
160+
clear() {
161+
this.dialogRef.close([]);
162+
}
163+
}

0 commit comments

Comments
 (0)