[ADF-1381] improving the style for form reflecting UX spec (#2256)

* [ADF-1381] improving the style for form reflecting UX spec

* [ADF-1381] fix error message icon

* [ADF-1381] added more spec details to form widgets

* [ADF-1381] fixed wrong check

* [ADF-1381] removed wrong added deep into scss

* [ADF-1381] applied changes due the peer review

* [ADF-1381] missed whitespace
This commit is contained in:
Vito
2017-08-30 09:02:55 -07:00
committed by Mario Romano
parent a58ced408c
commit 58c298fc25
31 changed files with 189 additions and 53 deletions

View File

@@ -14,5 +14,6 @@
placeholder="{{field.placeholder}}">
</md-input-container>
<error-widget [error]="field.validationSummary" ></error-widget>
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -56,7 +56,7 @@
}
.mat-input-placeholder {
top: 2.5em !important;
top: 2.2em !important;
}
.mat-focused {

View File

@@ -24,6 +24,7 @@ export class GroupUserModel {
firstName: string;
id: string;
lastName: string;
userImageUrl: string;
constructor(json?: any) {
if (json) {

View File

@@ -14,6 +14,7 @@
<button class="adf-date-widget-button" mdSuffix [mdDatepickerToggle]="datePicker" [disabled]="field.readOnly"></button>
</md-input-container>
<error-widget [error]="field.validationSummary" ></error-widget>
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
<md-datepicker #datePicker [touchUi]="true" [startAt]="startAt" (selectedChanged)="onDateChanged($event)"></md-datepicker>
</div>

View File

@@ -11,4 +11,5 @@
[id]="opt.id">{{opt.name}}</md-option>
</md-select>
<error-widget [error]="field.validationSummary" ></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -11,4 +11,8 @@
width: 100%;
}
&-dropdown-required-message .adf-error-text-container{
margin-top: -2px !important;
}
}

View File

@@ -62,4 +62,5 @@
(cancel)="onCancelChanges()">
</row-editor>
<error-widget [error]="field.validationSummary" ></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -3,4 +3,7 @@
<div class="adf-error-text">{{error}}</div>
<i class="material-icons adf-error-icon">warning</i>
</div>
<div *ngIf="required" [@transitionMessages]="_subscriptAnimationState">
<div class="adf-error-text">{{required}}</div>
</div>
</div>

View File

@@ -1,3 +1,5 @@
.adf-error-text-container {
margin-top: 0 !important;
margin-top: -16px !important;
position: inherit;
width: 100%;
}

View File

@@ -18,7 +18,7 @@
/* tslint:disable:component-selector */
import { animate, state, style, transition, trigger } from '@angular/animations';
import { AfterViewInit, Component, Input, ViewEncapsulation } from '@angular/core';
import { AfterViewInit, Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { FormService } from './../../../services/form.service';
import { baseHost , WidgetComponent } from './../widget.component';
@@ -38,11 +38,14 @@ import { baseHost , WidgetComponent } from './../widget.component';
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class ErrorWidgetComponent extends WidgetComponent implements AfterViewInit {
export class ErrorWidgetComponent extends WidgetComponent implements AfterViewInit, OnChanges {
@Input()
error: string;
@Input()
required: string;
_subscriptAnimationState: string = '';
constructor(public formService: FormService) {
@@ -53,4 +56,11 @@ export class ErrorWidgetComponent extends WidgetComponent implements AfterViewIn
this._subscriptAnimationState = 'enter';
}
ngOnChanges(changes: SimpleChanges) {
if (changes['required']) {
this.required = changes.required.currentValue;
this._subscriptAnimationState = 'enter';
}
}
}

View File

@@ -1,6 +1,6 @@
<div class="adf-group-widget {{field.className}}"
[class.is-dirty]="value"
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" id="typehead-div" *ngIf="field.isVisible">
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" id="functional-group-div" *ngIf="field.isVisible">
<md-input-container>
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<input mdInput
@@ -15,11 +15,12 @@
placeholder="{{field.placeholder}}">
</md-input-container>
<error-widget [error]="field.validationSummary"></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>
<div class="adf-typeahead-autocomplete mat-elevation-z2" *ngIf="popupVisible && users.length > 0">
<div class="adf-group-autocomplete mat-elevation-z2" *ngIf="popupVisible">
<md-option *ngFor="let item of groups"
[id]="field.id +'-'+item.id"
(click)="onItemClick(item, $event)">
<span>{{item.name}}}</span>
<span>{{item.name}}</span>
</md-option>
</div>

View File

@@ -9,13 +9,14 @@
&-group-autocomplete {
background-color: #fff;
position:absolute;
position: inherit;
max-height: 200px;
overflow-y: auto;
z-index: 5;
width: 100%;
color: #555;
margin: -15px 0 0 0;
> ul {
> md-option {
list-style-type: none;
position: static;
height: auto;
@@ -25,7 +26,7 @@
margin: 0;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
border-radius: 2px;
> li {
> span {
opacity: 1;
}
}

View File

@@ -6,6 +6,7 @@
md-autosize
type="text"
rows="3"
[maxlength]="field.maxLength"
[id]="field.id"
[required]="isRequired()"
[(ngModel)]="field.value"
@@ -14,6 +15,10 @@
placeholder="{{field.placeholder}}">
</textarea>
</md-input-container>
<div *ngIf="field.maxLength > 0" class="adf-multiline-word-counter">
<span>{{field?.value?.length || 0}}/{{field.maxLength}}</span>
</div>
<error-widget [error]="field.validationSummary"></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -7,4 +7,21 @@
width: 100%;
}
&-multiline-word-counter {
float: right;
margin-top: -20px !important;
min-height: 24px;
min-width: 1px;
font-size: 12px;
line-height: 14px;
overflow: hidden;
transition: all .3s cubic-bezier(.55,0,.55,.2);
opacity: 1;
margin-top: 0;
padding-top: 5px;
text-align: right;
padding-right: 2px;
padding-left: 0;
}
}

View File

@@ -15,4 +15,5 @@
placeholder="{{field.placeholder}}">
</md-input-container>
<error-widget [error]="field.validationSummary" ></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -1,13 +1,13 @@
<div class="adf-people-widget {{field.className}}"
[class.is-dirty]="value"
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" id="typehead-div" *ngIf="field.isVisible">
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" id="people-widget-content" *ngIf="field.isVisible">
<md-input-container>
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<input mdInput
class="adf-input"
type="text"
[id]="field.id"
[(ngModel)]="field.value"
[(ngModel)]="value"
(ngModelChange)="checkVisibility(field)"
(keyup)="onKeyUp($event)"
(blur)="onBlur()"
@@ -15,11 +15,22 @@
placeholder="{{field.placeholder}}">
</md-input-container>
<error-widget [error]="field.validationSummary"></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>
<div class="adf-typeahead-autocomplete mat-elevation-z2" *ngIf="popupVisible && users.length > 0">
<md-option *ngFor="let item of users"
[id]="field.id +'-'+item.id"
(click)="onItemClick(item, $event)">
<span>{{getDisplayName(item)}}</span>
<div class="adf-people-autocomplete mat-elevation-z2" *ngIf="popupVisible && users.length > 0">
<md-option *ngFor="let user of users"
[id]="field.id +'-'+user.id"
(click)="onItemClick(user, $event)">
<div class="adf-people-widget-row">
<div *ngIf="!user.userImage" class="adf-people-widget-pic">
{{user.firstName[0]}} {{user.lastName[0]}}
</div>
<div *ngIf="user.userImage" class="adf-people-widget-image-row">
<img class="adf-people-widget-image"
[src]="user.userImage"
(error)="onErrorImageLoad(user)"/>
</div>
<span>{{getDisplayName(user)}}</span>
</div>
</md-option>
</div>

View File

@@ -9,25 +9,58 @@
&-people-autocomplete {
background-color: #fff;
position:absolute;
position:inherit;
max-height: 200px;
width: 100%;
overflow-y: auto;
z-index: 5;
color: #555;
margin: -15px 0 0 0;
> ul {
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
> md-option {
list-style-type: none;
position: static;
height: auto;
width: auto;
min-width: 124px;
padding: 8px 0;
padding: 8px 0 0 20px;
margin: 0;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
border-radius: 2px;
> li {
> span {
opacity: 1;
}
}
}
&-people-widget-pic {
background: #ff9800;
display: inline-block;
width: 30px;
padding: 0px 9px;
border-radius: 100px;
color: #fff;
text-align: center;
font-weight: bolder;
font-size: 18px;
font-family: Muli;
text-transform: uppercase;
vertical-align: middle;
}
&-people-widget-row {
padding: 5px 5px;
}
&-people-widget-image {
border-radius: 100px;
width: 50px;
height: 50px;
vertical-align: middle;
display: inline-block;
padding: 0px 0px;
}
&-people-widget-image-row {
display: inline-block;
}
}

View File

@@ -85,12 +85,18 @@ export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
}, 200);
}
onErrorImageLoad(user) {
if (user.userImage) {
user.userImage = null;
}
}
flushValue() {
this.popupVisible = false;
let option = this.users.find(item => {
let fullName = this.getDisplayName(item).toLocaleLowerCase();
return fullName === this.value.toLocaleLowerCase();
return (this.value && fullName === this.value.toLocaleLowerCase());
});
if (option) {

View File

@@ -17,4 +17,7 @@
</md-radio-group>
</div>
<error-widget [error]="field.validationSummary" ></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -15,4 +15,5 @@
placeholder="{{field.placeholder}}">
</md-input-container>
<error-widget [error]="field.validationSummary"></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -1,23 +1,26 @@
<div class="adf-typeahead-widget {{field.className}}"
[class.is-dirty]="value"
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" id="typehead-div" *ngIf="field.isVisible">
<md-input-container>
<label class="adf-label" [attr.for]="field.id">{{field.name}}</label>
<input mdInput
class="adf-input"
type="text"
[id]="field.id"
[(ngModel)]="value"
(keyup)="onKeyUp($event)"
(blur)="onBlur()"
[disabled]="field.readOnly"
placeholder="{{field.placeholder}}">
</md-input-container>
<error-widget [error]="field.validationSummary" ></error-widget>
</div>
<div class="adf-typeahead-autocomplete mat-elevation-z2" *ngIf="options.length > 0 && popupVisible">
<md-option *ngFor="let item of options"
(click)="onItemClick(item, $event)">
<div class="adf-typeahead-widget-container">
<div class="adf-typeahead-widget {{field.className}}"
[class.is-dirty]="value"
[class.adf-invalid]="!field.isValid"
[class.adf-readonly]="field.readOnly"
id="typehead-div" *ngIf="field.isVisible">
<md-input-container>
<label class="adf-label" [attr.for]="field.id">{{field.name}}</label>
<input mdInput class="adf-input"
type="text"
[id]="field.id"
[(ngModel)]="value"
(keyup)="onKeyUp($event)"
(blur)="onBlur()"
[disabled]="field.readOnly"
placeholder="{{field.placeholder}}">
</md-input-container>
<error-widget [error]="field.validationSummary"></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>
<div class="adf-typeahead-autocomplete mat-elevation-z2" *ngIf="options.length > 0 && popupVisible">
<md-option *ngFor="let item of options" (click)="onItemClick(item, $event)">
<span>{{item.name}}</span>
</md-option>
</div>
</div>

View File

@@ -3,6 +3,11 @@
.adf {
&-typeahead-widget-container {
position: relative;
display: block;
}
&-typeahead-widget {
width: 100%;
}
@@ -11,11 +16,13 @@
background-color: #fff;
position:absolute;
max-height: 200px;
width: 100%;
overflow-y: auto;
z-index: 5;
color: #555;
margin: -15px 0 0 0;
> ul {
margin: -10px 0 0 0;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
> md-option {
list-style-type: none;
position: static;
height: auto;
@@ -23,10 +30,10 @@
min-width: 124px;
padding: 8px 0;
margin: 0;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
border-radius: 2px;
> li {
> span {
opacity: 1;
padding-left: 23px;
}
}
}

View File

@@ -28,5 +28,5 @@
(change)="onFileChanged($event)">
</div>
<error-widget [error]="field.validationSummary" ></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -83,6 +83,10 @@ export class WidgetComponent implements AfterViewInit {
this.field.value !== undefined;
}
isInvalidFieldRequired() {
return !this.field.isValid && !this.field.validationSummary && this.isRequired();
}
ngAfterViewInit() {
this.fieldChanged.emit(this.field);
}

View File

@@ -5,6 +5,9 @@
},
"PREVIEW": {
"IMAGE_NOT_AVAILABLE": "The preview image is not available."
},
"FIELD": {
"REQUIRED" : "*Required"
}
}
}

View File

@@ -477,6 +477,7 @@ describe('Form service', () => {
});
it('should return list of people', (done) => {
spyOn(service, 'getUserProfileImageApi').and.returnValue('/app/rest/users/2002/picture');
let fakeFilter: string = 'whatever';
service.getWorkflowUsers(fakeFilter).subscribe(result => {

View File

@@ -352,13 +352,22 @@ export class FormService {
return Observable.fromPromise(this.taskApi.getRestFieldValuesColumn(taskId, field, column)).catch(err => this.handleError(err));
}
private getUserProfileImageApi(userId: string): string {
return this.apiService.getInstance().activiti.userApi.getUserProfilePictureUrl(userId);
}
getWorkflowUsers(filter: string, groupId?: string): Observable<GroupUserModel[]> {
let option: any = {filter: filter};
if (groupId) {
option.groupId = groupId;
}
return Observable.fromPromise(this.usersWorkflowApi.getUsers(option))
.map((response: any) => <GroupUserModel[]> response.data || [])
.switchMap((response: any) => <GroupUserModel[]> response.data || [])
.map((user: any) => {
user.userImage = this.getUserProfileImageApi(user.id);
return Observable.of(user);
})
.combineAll()
.catch(err => this.handleError(err));
}