Browse Source

Bugfixes with autocompletion.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
5a8e33c5d4
  1. 9
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  2. 13
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss
  3. 12
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  4. 7
      src/Squidex/app/framework/angular/autocomplete.component.html
  5. 22
      src/Squidex/app/framework/angular/autocomplete.component.scss
  6. 53
      src/Squidex/app/framework/angular/autocomplete.component.ts
  7. 8
      src/Squidex/app/framework/angular/dropdown.component.html
  8. 25
      src/Squidex/app/framework/angular/dropdown.component.ts
  9. 16
      src/Squidex/app/framework/angular/template-wrapper.directive.ts

9
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -57,7 +57,14 @@
<div class="table-items-footer"> <div class="table-items-footer">
<form class="form-inline" [formGroup]="addContributorForm" (ngSubmit)="assignContributor()"> <form class="form-inline" [formGroup]="addContributorForm" (ngSubmit)="assignContributor()">
<div class="form-group mr-1"> <div class="form-group mr-1">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user"></sqx-autocomplete> <sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user" displayProperty="email">
<ng-template let-user="$implicit">
<img class="user-picture autocomplete-user-picture" [attr.src]="user | userDtoPicture" />
<span class="user-name autocomplete-user-name">{{user.displayName}}</span>
<span class="user-email autocomplete-user-email">{{user.email}}</span>
</ng-template>
</sqx-autocomplete>
</div> </div>
<button type="submit" class="btn btn-success" [disabled]="!canAddContributor">Add Contributor</button> <button type="submit" class="btn btn-success" [disabled]="!canAddContributor">Add Contributor</button>

13
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss

@ -1,2 +1,15 @@
@import '_vars'; @import '_vars';
@import '_mixins'; @import '_mixins';
.autocomplete-user {
&-picture {
float: left;
margin-top: .4rem;
}
&-name,
&-email {
@include truncate;
margin-left: 3rem;
}
}

12
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -15,7 +15,6 @@ import {
AppContributorsService, AppContributorsService,
AppsStoreService, AppsStoreService,
AuthService, AuthService,
AutocompleteItem,
AutocompleteSource, AutocompleteSource,
HistoryChannelUpdated, HistoryChannelUpdated,
ImmutableArray, ImmutableArray,
@ -32,19 +31,14 @@ export class UsersDataSource implements AutocompleteSource {
) { ) {
} }
public find(query: string): Observable<AutocompleteItem[]> { public find(query: string): Observable<any[]> {
return this.usersService.getUsers(query) return this.usersService.getUsers(query)
.map(users => { .map(users => {
const results: AutocompleteItem[] = []; const results: any[] = [];
for (let user of users) { for (let user of users) {
if (!this.component.appContributors || !this.component.appContributors.find(t => t.contributorId === user.id)) { if (!this.component.appContributors || !this.component.appContributors.find(t => t.contributorId === user.id)) {
results.push( results.push(user);
new AutocompleteItem(
user.displayName,
user.email,
user.pictureUrl!,
user));
} }
} }
return results; return results;

7
src/Squidex/app/framework/angular/autocomplete.component.html

@ -6,11 +6,10 @@
autocapitalize="off"> autocapitalize="off">
<div *ngIf="items.length > 0" [sqxModalTarget]="input" class="control-dropdown" #container> <div *ngIf="items.length > 0" [sqxModalTarget]="input" class="control-dropdown" #container>
<div *ngFor="let item of items; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === itemSelection" (mousedown)="chooseItem(item)" (mouseover)="selectIndex(i)" [sqxScrollActive]="i === itemSelection" [container]="container"> <div *ngFor="let item of items; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === selectedIndex" (mousedown)="selectItem(item)" (mouseover)="selectIndex(i)" [sqxScrollActive]="i === itemSelection" [container]="container">
<img class="control-dropdown-item-image" [attr.src]="item.image" /> <span *ngIf="!itemTemplate">{{item}}</span>
<span class="control-dropdown-item-title">{{item.title}}</span> <ng-template *ngIf="itemTemplate" [sqxTemplateWrapper]="itemTemplate" [item]="item" [index]="i"></ng-template>
<span class="control-dropdown-item-description">{{item.description}}</span>
</div> </div>
</div> </div>
</span> </span>

22
src/Squidex/app/framework/angular/autocomplete.component.scss

@ -1,28 +1,6 @@
@import '_mixins'; @import '_mixins';
@import '_vars'; @import '_vars';
$color-input-border: rgba(0, 0, 0, .15);
.control-dropdown { .control-dropdown {
& {
width: 18rem; width: 18rem;
} }
&-item {
&-image {
@include circle(2.5rem);
float: left;
}
&-title,
&-description {
@include truncate;
margin-left: 3rem;
}
&-description {
font-size: .8rem;
font-style: italic;
}
}
}

53
src/Squidex/app/framework/angular/autocomplete.component.ts

@ -5,22 +5,12 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, ContentChild, forwardRef, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
export interface AutocompleteSource { export interface AutocompleteSource {
find(query: string): Observable<AutocompleteItem[]>; find(query: string): Observable<any[]>;
}
export class AutocompleteItem {
constructor(
public readonly title: string,
public readonly description: string,
public readonly image: string,
public readonly model: any
) {
}
} }
const KEY_ENTER = 13; const KEY_ENTER = 13;
@ -50,11 +40,18 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
@Input() @Input()
public inputName = 'autocompletion'; public inputName = 'autocompletion';
@Input()
public displayProperty = '';
@Input() @Input()
public placeholder = ''; public placeholder = '';
public items: AutocompleteItem[] = []; @ContentChild(TemplateRef)
public itemSelection = -1; public itemTemplate: TemplateRef<any>;
public items: any[] = [];
public selectedIndex = -1;
public queryInput = new FormControl(); public queryInput = new FormControl();
@ -62,13 +59,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
if (!value) { if (!value) {
this.resetValue(); this.resetValue();
} else { } else {
let item: AutocompleteItem | null = null; let item = this.items.find(i => i === value);
if (value instanceof AutocompleteItem) {
item = value;
} else {
item = this.items.find(i => i.model === value);
}
if (item) { if (item) {
this.queryInput.setValue(value.title || ''); this.queryInput.setValue(value.title || '');
@ -133,7 +124,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
return false; return false;
case KEY_ENTER: case KEY_ENTER:
if (this.items.length > 0) { if (this.items.length > 0) {
this.chooseItem(); this.selectItem();
return false; return false;
} }
break; break;
@ -145,9 +136,9 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.touchedCallback(); this.touchedCallback();
} }
public chooseItem(selection: AutocompleteItem | null = null) { public selectItem(selection: any | null = null) {
if (!selection) { if (!selection) {
selection = this.items[this.itemSelection]; selection = this.items[this.selectedIndex];
} }
if (!selection && this.items.length === 1) { if (!selection && this.items.length === 1) {
@ -156,7 +147,11 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
if (selection) { if (selection) {
try { try {
this.queryInput.setValue(selection.title); if (this.displayProperty && this.displayProperty.length > 0) {
this.queryInput.setValue(selection[this.displayProperty], { emitEvent: false });
} else {
this.queryInput.setValue(selection.toString(), { emitEvent: false });
}
this.changeCallback(selection); this.changeCallback(selection);
} finally { } finally {
this.reset(); this.reset();
@ -165,11 +160,11 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
} }
private up() { private up() {
this.selectIndex(this.itemSelection - 1); this.selectIndex(this.selectedIndex - 1);
} }
private down() { private down() {
this.selectIndex(this.itemSelection + 1); this.selectIndex(this.selectedIndex + 1);
} }
private resetValue() { private resetValue() {
@ -185,11 +180,11 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
selection = this.items.length - 1; selection = this.items.length - 1;
} }
this.itemSelection = selection; this.selectedIndex = selection;
} }
private reset() { private reset() {
this.items = []; this.items = [];
this.itemSelection = -1; this.selectedIndex = -1;
} }
} }

8
src/Squidex/app/framework/angular/dropdown.component.html

@ -6,7 +6,9 @@
autocapitalize="off"> autocapitalize="off">
<div class="control-dropdown-item" *ngIf="selectedItem"> <div class="control-dropdown-item" *ngIf="selectedItem">
<ng-template [sqxTemplateWrapper]="itemTemplate" [item]="selectedItem"></ng-template> <span *ngIf="!selectionTemplate">{{selectedItem}}</span>
<ng-template *ngIf="selectionTemplate" [sqxTemplateWrapper]="selectionTemplate" [item]="selectedItem"></ng-template>
</div> </div>
<i class="icon-caret-down"></i> <i class="icon-caret-down"></i>
@ -15,7 +17,9 @@
<div class="items-container"> <div class="items-container">
<div class="control-dropdown" #container *sqxModalView="dropdown" [sqxModalTarget]="input"> <div class="control-dropdown" #container *sqxModalView="dropdown" [sqxModalTarget]="input">
<div *ngFor="let item of items; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === selectedIndex" (mousedown)="selectIndexAndClose(i)" [sqxScrollActive]="i === selectedIndex" [container]="container"> <div *ngFor="let item of items; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === selectedIndex" (mousedown)="selectIndexAndClose(i)" [sqxScrollActive]="i === selectedIndex" [container]="container">
<ng-template [sqxTemplateWrapper]="itemTemplate" [item]="item" [index]="i"></ng-template> <span *ngIf="!itemTemplate">{{item}}</span>
<ng-template *ngIf="itemTemplate" [sqxTemplateWrapper]="itemTemplate" [item]="item" [index]="i"></ng-template>
</div> </div>
</div> </div>
</div> </div>

25
src/Squidex/app/framework/angular/dropdown.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Component, ContentChild, forwardRef, Input, TemplateRef } from '@angular/core'; import { AfterContentInit, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const KEY_ENTER = 13; const KEY_ENTER = 13;
@ -26,25 +26,38 @@ export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
templateUrl: './dropdown.component.html', templateUrl: './dropdown.component.html',
providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR] providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR]
}) })
export class DropdownComponent implements ControlValueAccessor { export class DropdownComponent implements AfterContentInit, ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP; private changeCallback: (value: any) => void = NOOP;
private touchedCallback: () => void = NOOP; private touchedCallback: () => void = NOOP;
@Input() @Input()
public items: any[] = []; public items: any[] = [];
@ContentChild(TemplateRef) @ContentChildren(TemplateRef)
public itemTemplate: TemplateRef<any>; public templates: QueryList<any>;
public dropdown = new ModalView(); public dropdown = new ModalView();
public selectedItem: any; public selectedItem: any;
public selectedIndex = -1; public selectedIndex = -1;
public selectionTemplate: TemplateRef<any>;
public itemTemplate: TemplateRef<any>;
public isDisabled = false; public isDisabled = false;
private get safeItems(): any[] { public ngAfterContentInit() {
return this.items || []; if (this.templates.length === 1) {
this.itemTemplate = this.selectionTemplate = this.templates.first;
} else {
this.templates.forEach(template => {
if (template.name === 'selection') {
this.selectionTemplate = template;
} else {
this.itemTemplate = template;
}
});
}
} }
public writeValue(value: any) { public writeValue(value: any) {

16
src/Squidex/app/framework/angular/template-wrapper.directive.ts

@ -27,13 +27,6 @@ export class TemplateWrapper implements OnInit, OnDestroy, OnChanges {
) { ) {
} }
public ngOnChanges() {
if (this.view) {
this.view.context.$implicit = this.item;
this.view.context.index = this.index;
}
}
public ngOnInit() { public ngOnInit() {
this.view = this.viewContainer.createEmbeddedView(this.templateRef, { this.view = this.viewContainer.createEmbeddedView(this.templateRef, {
'\$implicit': this.item, '\$implicit': this.item,
@ -41,7 +34,16 @@ export class TemplateWrapper implements OnInit, OnDestroy, OnChanges {
}); });
} }
public ngOnChanges() {
if (this.view) {
this.view.context.$implicit = this.item;
this.view.context.index = this.index;
}
}
public ngOnDestroy() { public ngOnDestroy() {
if (this.view) {
this.view.destroy(); this.view.destroy();
} }
} }
}
Loading…
Cancel
Save