[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

@@ -21,7 +21,7 @@
margin-bottom: 5px; margin-bottom: 5px;
} }
.adf-no-form-container { .adf-no-form-container {
text-align: center; text-align: center;
font-weight: 600; font-weight: 600;
font-size: 18px; font-size: 18px;

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@
<button class="adf-date-widget-button" mdSuffix [mdDatepickerToggle]="datePicker" [disabled]="field.readOnly"></button> <button class="adf-date-widget-button" mdSuffix [mdDatepickerToggle]="datePicker" [disabled]="field.readOnly"></button>
</md-input-container> </md-input-container>
<error-widget [error]="field.validationSummary" ></error-widget> <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> <md-datepicker #datePicker [touchUi]="true" [startAt]="startAt" (selectedChanged)="onDateChanged($event)"></md-datepicker>
</div> </div>

View File

@@ -11,4 +11,5 @@
[id]="opt.id">{{opt.name}}</md-option> [id]="opt.id">{{opt.name}}</md-option>
</md-select> </md-select>
<error-widget [error]="field.validationSummary" ></error-widget> <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>

View File

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

View File

@@ -62,4 +62,5 @@
(cancel)="onCancelChanges()"> (cancel)="onCancelChanges()">
</row-editor> </row-editor>
<error-widget [error]="field.validationSummary" ></error-widget> <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>

View File

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

View File

@@ -1,3 +1,5 @@
.adf-error-text-container { .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 */ /* tslint:disable:component-selector */
import { animate, state, style, transition, trigger } from '@angular/animations'; 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 { FormService } from './../../../services/form.service';
import { baseHost , WidgetComponent } from './../widget.component'; import { baseHost , WidgetComponent } from './../widget.component';
@@ -38,11 +38,14 @@ import { baseHost , WidgetComponent } from './../widget.component';
host: baseHost, host: baseHost,
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class ErrorWidgetComponent extends WidgetComponent implements AfterViewInit { export class ErrorWidgetComponent extends WidgetComponent implements AfterViewInit, OnChanges {
@Input() @Input()
error: string; error: string;
@Input()
required: string;
_subscriptAnimationState: string = ''; _subscriptAnimationState: string = '';
constructor(public formService: FormService) { constructor(public formService: FormService) {
@@ -53,4 +56,11 @@ export class ErrorWidgetComponent extends WidgetComponent implements AfterViewIn
this._subscriptAnimationState = 'enter'; 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}}" <div class="adf-group-widget {{field.className}}"
[class.is-dirty]="value" [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> <md-input-container>
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label> <label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<input mdInput <input mdInput
@@ -15,11 +15,12 @@
placeholder="{{field.placeholder}}"> placeholder="{{field.placeholder}}">
</md-input-container> </md-input-container>
<error-widget [error]="field.validationSummary"></error-widget> <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>
<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" <md-option *ngFor="let item of groups"
[id]="field.id +'-'+item.id" [id]="field.id +'-'+item.id"
(click)="onItemClick(item, $event)"> (click)="onItemClick(item, $event)">
<span>{{item.name}}}</span> <span>{{item.name}}</span>
</md-option> </md-option>
</div> </div>

View File

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

View File

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

View File

@@ -7,4 +7,21 @@
width: 100%; 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}}"> placeholder="{{field.placeholder}}">
</md-input-container> </md-input-container>
<error-widget [error]="field.validationSummary" ></error-widget> <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>

View File

@@ -1,13 +1,13 @@
<div class="adf-people-widget {{field.className}}" <div class="adf-people-widget {{field.className}}"
[class.is-dirty]="value" [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> <md-input-container>
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label> <label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<input mdInput <input mdInput
class="adf-input" class="adf-input"
type="text" type="text"
[id]="field.id" [id]="field.id"
[(ngModel)]="field.value" [(ngModel)]="value"
(ngModelChange)="checkVisibility(field)" (ngModelChange)="checkVisibility(field)"
(keyup)="onKeyUp($event)" (keyup)="onKeyUp($event)"
(blur)="onBlur()" (blur)="onBlur()"
@@ -15,11 +15,22 @@
placeholder="{{field.placeholder}}"> placeholder="{{field.placeholder}}">
</md-input-container> </md-input-container>
<error-widget [error]="field.validationSummary"></error-widget> <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>
<div class="adf-typeahead-autocomplete mat-elevation-z2" *ngIf="popupVisible && users.length > 0"> <div class="adf-people-autocomplete mat-elevation-z2" *ngIf="popupVisible && users.length > 0">
<md-option *ngFor="let item of users" <md-option *ngFor="let user of users"
[id]="field.id +'-'+item.id" [id]="field.id +'-'+user.id"
(click)="onItemClick(item, $event)"> (click)="onItemClick(user, $event)">
<span>{{getDisplayName(item)}}</span> <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> </md-option>
</div> </div>

View File

@@ -9,25 +9,58 @@
&-people-autocomplete { &-people-autocomplete {
background-color: #fff; background-color: #fff;
position:absolute; position:inherit;
max-height: 200px; max-height: 200px;
width: 100%;
overflow-y: auto; overflow-y: auto;
z-index: 5; z-index: 5;
color: #555; color: #555;
margin: -15px 0 0 0; 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; list-style-type: none;
position: static; position: static;
height: auto; height: auto;
width: auto; width: auto;
min-width: 124px; min-width: 124px;
padding: 8px 0; padding: 8px 0 0 20px;
margin: 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; border-radius: 2px;
> li { > span {
opacity: 1; 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); }, 200);
} }
onErrorImageLoad(user) {
if (user.userImage) {
user.userImage = null;
}
}
flushValue() { flushValue() {
this.popupVisible = false; this.popupVisible = false;
let option = this.users.find(item => { let option = this.users.find(item => {
let fullName = this.getDisplayName(item).toLocaleLowerCase(); let fullName = this.getDisplayName(item).toLocaleLowerCase();
return fullName === this.value.toLocaleLowerCase(); return (this.value && fullName === this.value.toLocaleLowerCase());
}); });
if (option) { if (option) {

View File

@@ -17,4 +17,7 @@
</md-radio-group> </md-radio-group>
</div> </div>
<error-widget [error]="field.validationSummary" ></error-widget> <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>

View File

@@ -15,4 +15,5 @@
placeholder="{{field.placeholder}}"> placeholder="{{field.placeholder}}">
</md-input-container> </md-input-container>
<error-widget [error]="field.validationSummary"></error-widget> <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>

View File

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

View File

@@ -3,6 +3,11 @@
.adf { .adf {
&-typeahead-widget-container {
position: relative;
display: block;
}
&-typeahead-widget { &-typeahead-widget {
width: 100%; width: 100%;
} }
@@ -11,11 +16,13 @@
background-color: #fff; background-color: #fff;
position:absolute; position:absolute;
max-height: 200px; max-height: 200px;
width: 100%;
overflow-y: auto; overflow-y: auto;
z-index: 5; z-index: 5;
color: #555; color: #555;
margin: -15px 0 0 0; margin: -10px 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; list-style-type: none;
position: static; position: static;
height: auto; height: auto;
@@ -23,10 +30,10 @@
min-width: 124px; min-width: 124px;
padding: 8px 0; padding: 8px 0;
margin: 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; border-radius: 2px;
> li { > span {
opacity: 1; opacity: 1;
padding-left: 23px;
} }
} }
} }

View File

@@ -28,5 +28,5 @@
(change)="onFileChanged($event)"> (change)="onFileChanged($event)">
</div> </div>
<error-widget [error]="field.validationSummary" ></error-widget> <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>

View File

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

View File

@@ -5,6 +5,9 @@
}, },
"PREVIEW": { "PREVIEW": {
"IMAGE_NOT_AVAILABLE": "The preview image is not available." "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) => { it('should return list of people', (done) => {
spyOn(service, 'getUserProfileImageApi').and.returnValue('/app/rest/users/2002/picture');
let fakeFilter: string = 'whatever'; let fakeFilter: string = 'whatever';
service.getWorkflowUsers(fakeFilter).subscribe(result => { 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)); 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[]> { getWorkflowUsers(filter: string, groupId?: string): Observable<GroupUserModel[]> {
let option: any = {filter: filter}; let option: any = {filter: filter};
if (groupId) { if (groupId) {
option.groupId = groupId; option.groupId = groupId;
} }
return Observable.fromPromise(this.usersWorkflowApi.getUsers(option)) 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)); .catch(err => this.handleError(err));
} }

View File

@@ -79,7 +79,9 @@ export class CommentListComponent {
} }
onErrorImageLoad(user: User) { onErrorImageLoad(user: User) {
user.userImage = null; if (user.userImage) {
user.userImage = null;
}
} }
} }

View File

@@ -106,6 +106,8 @@ export class PeopleSearchComponent implements OnInit {
} }
onErrorImageLoad(user: User) { onErrorImageLoad(user: User) {
user.userImage = null; if (user.userImage) {
user.userImage = null;
}
} }
} }

View File

@@ -156,6 +156,8 @@ export class PeopleComponent implements OnInit, AfterViewInit {
} }
onErrorImageLoad(user: User) { onErrorImageLoad(user: User) {
user.userImage = null; if (user.userImage) {
user.userImage = null;
}
} }
} }