Dataset Profile editor. Drag and drop navigation table of contents.

This commit is contained in:
Kristian Ntavidi 2021-03-01 11:28:27 +02:00
parent da7f91df96
commit 081995e243
10 changed files with 860 additions and 156 deletions

View File

@ -1,5 +1,5 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { Component, Input, OnChanges, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { FormArray, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
import { FieldEditorModel } from '../../../admin/field-editor-model'; import { FieldEditorModel } from '../../../admin/field-editor-model';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { RuleEditorModel } from '../../../admin/rule-editor-model'; import { RuleEditorModel } from '../../../admin/rule-editor-model';
@ -184,6 +184,8 @@ export class DatasetProfileEditorCompositeFieldComponent implements OnInit, OnCh
field.ordinal = (this.form.get('fields') as FormArray).length; field.ordinal = (this.form.get('fields') as FormArray).length;
const fieldForm = field.buildForm(); const fieldForm = field.buildForm();
fieldForm.setValidators(this.customFieldValidator());
// fieldForm.get('viewStyle').get('renderStyle').setValidators(Validators.required);
(<FormArray>this.form.get('fields')).push(fieldForm); (<FormArray>this.form.get('fields')).push(fieldForm);
this.setTargetField(fieldForm); this.setTargetField(fieldForm);
@ -329,6 +331,8 @@ export class DatasetProfileEditorCompositeFieldComponent implements OnInit, OnCh
field.ordinal = (this.form.get('fields') as FormArray).length; field.ordinal = (this.form.get('fields') as FormArray).length;
const fieldForm = field.buildForm(); const fieldForm = field.buildForm();
fieldForm.setValidators(this.customFieldValidator());
// fieldForm.get('viewStyle').get('renderStyle').setValidators(Validators.required);
@ -421,5 +425,51 @@ export class DatasetProfileEditorCompositeFieldComponent implements OnInit, OnCh
} }
private customFieldValidator(): ValidatorFn{
return (control):ValidationErrors | null=>{
DatasetProfileFieldViewStyle
switch(control.get('viewStyle').get('renderStyle').value){
case DatasetProfileFieldViewStyle.TextArea:
return null;
case DatasetProfileFieldViewStyle.BooleanDecision:
return null;
case DatasetProfileFieldViewStyle.ComboBox:
return null;
case DatasetProfileFieldViewStyle.CheckBox:
return null;
case DatasetProfileFieldViewStyle.FreeText:
return null;
case DatasetProfileFieldViewStyle.RadioBox:
return null;
case DatasetProfileFieldViewStyle.DatePicker:
return null;
case DatasetProfileFieldViewStyle.InternalDmpEntities:
return null;
case DatasetProfileFieldViewStyle.ExternalDatasets:
return null;
case DatasetProfileFieldViewStyle.DataRepositories:
return null;
case DatasetProfileFieldViewStyle.Registries:
return null;
case DatasetProfileFieldViewStyle.Services:
return null;
case DatasetProfileFieldViewStyle.Tags:
return null;
case DatasetProfileFieldViewStyle.Researchers:
return null;
case DatasetProfileFieldViewStyle.Organizations:
return null;
case DatasetProfileFieldViewStyle.DatasetIdentifier:
return null;
case DatasetProfileFieldViewStyle.Currency:
return null;
case DatasetProfileFieldViewStyle.Validation:
return null;
default:
return {inputTypeNotValid: true}
}
}
}
} }

View File

@ -68,9 +68,10 @@
</mat-select> --> </mat-select> -->
<!-- NEW VERSION --> <!-- NEW VERSION -->
<mat-select placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.VIEW-STYLE' | translate}}" [(value)]="viewType" (selectionChange)="onInputTypeChange()" <mat-select placeholder="{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.VIEW-STYLE' | translate}}" [(ngModel)]="viewType" (selectionChange)="onInputTypeChange()"
[disabled]="viewOnly" [disabled]="viewOnly"
required> required
>
<mat-option [value]="viewTypeEnum.TextArea">{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.TextArea)}}</mat-option> <mat-option [value]="viewTypeEnum.TextArea">{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.TextArea)}}</mat-option>
<mat-option [value]="viewTypeEnum.FreeText">{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.FreeText)}}</mat-option> <mat-option [value]="viewTypeEnum.FreeText">{{enumUtils.toDatasetProfileViewTypeString(viewTypeEnum.FreeText)}}</mat-option>
<mat-divider></mat-divider> <mat-divider></mat-divider>
@ -107,6 +108,8 @@
</mat-select> </mat-select>
<mat-error *ngIf="this.form.get('viewStyle').get('renderStyle').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="this.form.get('viewStyle').get('renderStyle').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
<mat-error *ngIf="this.form.hasError('inputTypeNotValid')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field> </mat-form-field>
<!-- Combo Box --> <!-- Combo Box -->
@ -209,4 +212,5 @@
</em> </em>
</div> </div>
</div> </div>
</ng-container> </ng-container>
{{form.touched|json}}

View File

@ -1,6 +1,6 @@
 
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { AbstractControl, AbstractControlOptions, FormArray, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms';
import { DatasetProfileFieldViewStyle } from '@app/core/common/enum/dataset-profile-field-view-style'; import { DatasetProfileFieldViewStyle } from '@app/core/common/enum/dataset-profile-field-view-style';
import { ValidationType } from '@app/core/common/enum/validation-type'; import { ValidationType } from '@app/core/common/enum/validation-type';
import { DatasetProfileService } from '@app/core/services/dataset-profile/dataset-profile.service'; import { DatasetProfileService } from '@app/core/services/dataset-profile/dataset-profile.service';
@ -34,7 +34,7 @@ import { AutoCompleteFieldDataEditorModel } from '../../../admin/field-data/auto
import { DatasetsAutoCompleteFieldDataEditorModel } from '../../../admin/field-data/datasets-autocomplete-field-data-editor-mode'; import { DatasetsAutoCompleteFieldDataEditorModel } from '../../../admin/field-data/datasets-autocomplete-field-data-editor-mode';
import { DatasetProfileComboBoxType } from '@app/core/common/enum/dataset-profile-combo-box-type'; import { DatasetProfileComboBoxType } from '@app/core/common/enum/dataset-profile-combo-box-type';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { MatSlideToggleChange } from '@angular/material'; import { ErrorStateMatcher, MatSlideToggleChange } from '@angular/material';
@Component({ @Component({
selector: 'app-dataset-profile-editor-field-component', selector: 'app-dataset-profile-editor-field-component',
@ -501,4 +501,5 @@ export class DatasetProfileEditorFieldComponent extends BaseComponent implements
onDelete(){ onDelete(){
this.delete.emit(); this.delete.emit();
} }
}
}

View File

@ -46,7 +46,8 @@
</button> </button>
<ng-container *ngIf="stepper.selectedIndex === (steps.length-1)"> <ng-container *ngIf="false">
<!-- <ng-container *ngIf="stepper.selectedIndex === (steps.length-1)"> -->
<ng-container *ngIf="!viewOnly"> <ng-container *ngIf="!viewOnly">
<button mat-button class="navigate-btn ml-2" <button mat-button class="navigate-btn ml-2"
@ -350,11 +351,17 @@
</ng-container> </ng-container>
<!-- <div class="row"> <div class="row">
<button (click)="printForm()"> <button (click)="printForm()">
console form console form
</button> </button>
</div> --> </div>
<div class="row">
<button (click)="printMyErrors()">
print errors
</button>
</div>
<!-- <div class="row"> <!-- <div class="row">
<button (click)="foo()">foo</button> <button (click)="foo()">foo</button>
</div> --> </div> -->

View File

@ -2,7 +2,7 @@
import { of as observableOf, Observable } from 'rxjs'; import { of as observableOf, Observable } from 'rxjs';
import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { AfterViewInit, Component, OnChanges, OnInit, QueryList, SimpleChanges, ViewChild } from '@angular/core'; import { AfterViewInit, Component, OnChanges, OnInit, QueryList, SimpleChanges, ViewChild } from '@angular/core';
import { Form, FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { AbstractControl, Form, FormArray, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatHorizontalStepper, MatStep } from '@angular/material/stepper'; import { MatHorizontalStepper, MatStep } from '@angular/material/stepper';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, ParamMap, Router } from '@angular/router';
@ -38,7 +38,7 @@ import { VisibilityRulesService } from '@app/ui/misc/dataset-description-form/vi
import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper'; import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper';
import { DatasetDescriptionCompositeFieldEditorModel, DatasetDescriptionFieldEditorModel, DatasetDescriptionFormEditorModel, DatasetDescriptionPageEditorModel, DatasetDescriptionSectionEditorModel } from '@app/ui/misc/dataset-description-form/dataset-description-form.model'; import { DatasetDescriptionCompositeFieldEditorModel, DatasetDescriptionFieldEditorModel, DatasetDescriptionFormEditorModel, DatasetDescriptionPageEditorModel, DatasetDescriptionSectionEditorModel } from '@app/ui/misc/dataset-description-form/dataset-description-form.model';
import { Rule } from '@app/core/model/dataset-profile-definition/rule'; import { Rule } from '@app/core/model/dataset-profile-definition/rule';
import { DatasetProfileFieldViewStyle } from '@app/core/common/enum/dataset-profile-field-view-style';
const skipDisable: any[] = require('../../../../../assets/resources/skipDisable.json'); const skipDisable: any[] = require('../../../../../assets/resources/skipDisable.json');
@Component({ @Component({
@ -532,8 +532,6 @@ export class DatasetProfileEditorComponent extends BaseComponent implements OnIn
if(this.selectedTocEntry){ if(this.selectedTocEntry){
this.selectedTocEntry = this._findTocEntryById(this.selectedTocEntry.id, this.toCEntries); this.selectedTocEntry = this._findTocEntryById(this.selectedTocEntry.id, this.toCEntries);
} }
this.updateOrdinals(this.toCEntries); this.updateOrdinals(this.toCEntries);
return this.toCEntries; return this.toCEntries;
} }
@ -551,6 +549,22 @@ export class DatasetProfileEditorComponent extends BaseComponent implements OnIn
}); });
} }
//sort tocentries based on their ordinality
private _sortToCentries(entries: ToCEntry[]){
if(!entries || !entries.length) return;
entries.sort(this._compareOrdinals);
entries.forEach(e=>{
this._sortToCentries(e.subEntries)
});
}
private _compareOrdinals(a, b){
const aValue = a.form.get('ordinal').value as number;
const bValue = b.form.get('ordinal').value as number;
// if(!aValue || !bValue) return 0;
return aValue - bValue;
}
toCEntries:ToCEntry[]; toCEntries:ToCEntry[];
@ -601,6 +615,7 @@ export class DatasetProfileEditorComponent extends BaseComponent implements OnIn
pageToAdd.subEntries.push(item); pageToAdd.subEntries.push(item);
}); });
this._sortToCentries(result);
return result; return result;
} }
@ -678,9 +693,7 @@ export class DatasetProfileEditorComponent extends BaseComponent implements OnIn
} }
} }
if(tocEntryFound) return tocEntryFound; return tocEntryFound? tocEntryFound: null;
return null;
} }
addNewEntry(tce: Foo) { addNewEntry(tce: Foo) {
@ -688,81 +701,83 @@ export class DatasetProfileEditorComponent extends BaseComponent implements OnIn
//define entry type //define entry type
switch (tce.childType) { switch (tce.childType) {
case ToCEntryType.Page: //CALLED FROM PAGE case ToCEntryType.Page:
//create section
// this.addPage();
// this.displayItem(child);
//ceate page editor model and give ordinal
const page: PageEditorModel = new PageEditorModel(this.dataModel.pages.length); const page: PageEditorModel = new PageEditorModel(this.dataModel.pages.length);
this.dataModel.pages.push(page); const pageForm = page.buildForm();
(<FormArray>this.form.get('pages')).push(page.buildForm()); // this.dataModel.pages.push(page);
//make new entry selected
const pagesArray = (this.form.get('pages') as FormArray); const pagesArray = (this.form.get('pages') as FormArray);
const addedEntry = pagesArray.at(pagesArray.length-1) as FormGroup; pagesArray.push(pageForm);
this.selectedTocEntry = this._findTocEntryById(addedEntry.get('id').value, this.getTocEntries()); this.refreshToCEntries();
this.selectedTocEntry = this._findTocEntryById(pageForm.get('id').value, this.toCEntries);
break; break;
case ToCEntryType.Section: //adding a section case ToCEntryType.Section:
const section: SectionEditorModel = new SectionEditorModel(); const section: SectionEditorModel = new SectionEditorModel();
//give id
section.id = Guid.create().toString(); section.id = Guid.create().toString();
let sectionsArray:FormArray; let sectionsArray:FormArray;
//TODO CHECK FOR FORM.ROOT ERROR if (parent.type === ToCEntryType.Page) {//FIRST LEVEL SECTION
if (parent.type === ToCEntryType.Page) {
//FIRST LEVEL SECTION
//give ordinal and link to parent
section.page = parent.id;
section.ordinal = (this.form.get('sections') as FormArray).length;
(<FormArray>this.form.get('sections')).push(section.buildForm());
sectionsArray = this.form.get('sections') as FormArray; sectionsArray = this.form.get('sections') as FormArray;
} else if( parent.type == ToCEntryType.Section) { //subsection
section.page = parent.id;
section.ordinal = sectionsArray.length;
sectionsArray.push(section.buildForm());
} else if( parent.type == ToCEntryType.Section) { //SUBSECTION OF SECTION
sectionsArray = parent.form.get('sections') as FormArray;
//adding page parent MAYBE NOT NEEDED //adding page parent MAYBE NOT NEEDED
section.page = parent.form.get('page').value; section.page = parent.form.get('page').value;
//MAYBE NOT NEEDED //MAYBE NOT NEEDED
section.ordinal = (parent.form.get('sections') as FormArray).length; section.ordinal = sectionsArray.length;
(<FormArray>parent.form.get('sections')).push(section.buildForm()); sectionsArray.push(section.buildForm());
// (child.form.parent as FormArray).push(section.buildForm()); // (child.form.parent as FormArray).push(section.buildForm());
sectionsArray = parent.form.get('sections') as FormArray;
}else{ }else{
console.error('BUg found'); console.error('Section can only bee child of a page or another section');
} }
const sectionAdded = sectionsArray.at(sectionsArray.length -1) as FormGroup; const sectionAdded = sectionsArray.at(sectionsArray.length -1) as FormGroup;
this.selectedTocEntry = this._findTocEntryById(sectionAdded.get('id').value, this.getTocEntries());
this.refreshToCEntries();
this.selectedTocEntry = this._findTocEntryById(sectionAdded.get('id').value, this.toCEntries);
break; break;
case ToCEntryType.FieldSet: case ToCEntryType.FieldSet:
const fieldSet: FieldSetEditorModel = new FieldSetEditorModel();
//create one field form fieldset //create one field form fieldset
const field: FieldEditorModel = new FieldEditorModel(); //to ask const field: FieldEditorModel = new FieldEditorModel();
field.id = Guid.create().toString(); field.id = Guid.create().toString();
field.ordinal = 0;//first filed in the fields list field.ordinal = 0;//first filed in the fields list
fieldSet.fields.push(field); const fieldForm = field.buildForm();
fieldForm.setValidators(this.customFieldValidator());
// fieldForm.get('viewStyle').get('renderStyle').setValidators(Validators.required);
// fieldSet.fields.push(field);
// field.ordinal = fieldSet.fields.length-1; // field.ordinal = fieldSet.fields.length-1;
const fieldSetsArray = parent.form.get('fieldSets') as FormArray
//give fieldset id and ordinal //give fieldset id and ordinal
fieldSet.id = Guid.create().toString(); const fieldSet: FieldSetEditorModel = new FieldSetEditorModel();
fieldSet.ordinal = (parent.form.get('fieldSets') as FormArray).length; const fieldSetId = Guid.create().toString();
fieldSet.id = fieldSetId;
fieldSet.ordinal = fieldSetsArray.length;
const fieldsetForm = fieldSet.buildForm();
(<FormArray>parent.form.get('fieldSets')).push(fieldSet.buildForm());
const parentArray = parent.form.get('fieldSets') as FormArray; (fieldsetForm.get('fields') as FormArray).push(fieldForm);
const addedFieldSet = parentArray.at(parentArray.length - 1); fieldSetsArray.push(fieldsetForm);
this.selectedTocEntry = this._findTocEntryById(addedFieldSet.get('id').value, this.getTocEntries()); this.refreshToCEntries();
this.selectedTocEntry = this._findTocEntryById(fieldSetId, this.toCEntries);
break; break;
@ -770,10 +785,57 @@ export class DatasetProfileEditorComponent extends BaseComponent implements OnIn
break; break;
} }
this.refreshToCEntries(); // this.refreshToCEntries();
} }
private customFieldValidator(): ValidatorFn{
return (control):ValidationErrors | null=>{
DatasetProfileFieldViewStyle
switch(control.get('viewStyle').get('renderStyle').value){
case DatasetProfileFieldViewStyle.TextArea:
return null;
case DatasetProfileFieldViewStyle.BooleanDecision:
return null;
case DatasetProfileFieldViewStyle.ComboBox:
return null;
case DatasetProfileFieldViewStyle.CheckBox:
return null;
case DatasetProfileFieldViewStyle.FreeText:
return null;
case DatasetProfileFieldViewStyle.RadioBox:
return null;
case DatasetProfileFieldViewStyle.DatePicker:
return null;
case DatasetProfileFieldViewStyle.InternalDmpEntities:
return null;
case DatasetProfileFieldViewStyle.ExternalDatasets:
return null;
case DatasetProfileFieldViewStyle.DataRepositories:
return null;
case DatasetProfileFieldViewStyle.Registries:
return null;
case DatasetProfileFieldViewStyle.Services:
return null;
case DatasetProfileFieldViewStyle.Tags:
return null;
case DatasetProfileFieldViewStyle.Researchers:
return null;
case DatasetProfileFieldViewStyle.Organizations:
return null;
case DatasetProfileFieldViewStyle.DatasetIdentifier:
return null;
case DatasetProfileFieldViewStyle.Currency:
return null;
case DatasetProfileFieldViewStyle.Validation:
return null;
default:
return {inputTypeNotValid: true};
}
}
}
onRemoveEntry(tce: ToCEntry){ onRemoveEntry(tce: ToCEntry){
@ -1282,7 +1344,6 @@ export class DatasetProfileEditorComponent extends BaseComponent implements OnIn
} }
cloneFieldSet(fieldset: FormGroup){ cloneFieldSet(fieldset: FormGroup){
//TODO
const values = fieldset.getRawValue(); const values = fieldset.getRawValue();
const parentArray = fieldset.parent as FormArray; const parentArray = fieldset.parent as FormArray;
@ -1332,4 +1393,124 @@ export class DatasetProfileEditorComponent extends BaseComponent implements OnIn
this.checkFormValidation(); this.checkFormValidation();
} }
} }
getFormValidationErrors() {
Object.keys(this.form.controls).forEach(key => {
const controlErrors: ValidationErrors = this.form.get(key).errors;
if (controlErrors != null) {
Object.keys(controlErrors).forEach(keyError => {
console.log('Key control: ' + key + ', keyError: ' + keyError + ', err value: ', controlErrors[keyError]);
});
}
});
if(this.form.invalid){
console.log('this form is invalid!');
console.log(this.form.errors);
}
}
printMyErrors(){
// this._printToCentriesErrrors(this.toCEntries);
this._getErrors(this.form);
}
private _printToCentriesErrrors(entries: ToCEntry[]){
if(!entries || !entries.length) return;
entries.forEach(e=>{
// if(e.form instanceof FormGroup) console.log('is Formgroup');
// if(e.form instanceof FormControl) console.log('is formcontrols');
// if(e.form instanceof FormArray) console.log('isForm array');
const form = e.form as FormGroup;
if(form.invalid){
const controls = form.controls;
const keys = Object.keys(controls);
keys.forEach(key=>{
const control = controls[key];
if(control.invalid){
console.log('control ', key, 'is invalid');
}
})
// console.log('keys,', keys);
this._printToCentriesErrrors(e.subEntries);
}
});
}
private _getErrors(aControl: AbstractControl){
if(aControl instanceof FormGroup) console.log('is Formgroup');
if(aControl instanceof FormControl)console.log( aControl.errors);
if(aControl instanceof FormArray) console.log('isForm array');
if(aControl.valid) return;
let controlType = 'control';
if(aControl instanceof FormGroup) controlType="fg"
if(aControl instanceof FormControl) controlType="fc";
if(aControl instanceof FormArray) controlType="fa";
//invalid
switch (controlType){
case 'fg':
const controls = (aControl as FormGroup).controls;
const keys = Object.keys(controls);
keys.forEach(key=>{
const control = controls[key];
// if(control.invalid){
this._getErrors(control);
// }
});
break;
case 'fc':
//form control print errors
console.log(aControl.errors);
break;
case 'fa':
const fa = (aControl as FormArray);
fa.controls.forEach(control=>{
this._getErrors(control);
});
break;
}
// const controls = rootForm.controls;
// const keys = Object.keys(controls);
// keys.forEach(key=>{
// const control = controls[key];
// if(control.invalid){
// console.log('control ', key, 'is invalid');
// }
// })
}
} }

View File

@ -33,7 +33,7 @@
</div> </div>
<div class="col-auto d-flex align-items-center" > <div class="col-auto d-flex align-items-center" >
<span class="badge-ball" <span class="badge-ball"
*ngIf="!(!((parentLink?.subEntriesType == tocEntryType.FieldSet) && !selectedItemInLinks) || itemSelected?.id == parentLink.id)"> *ngIf="!(!((parentLink?.subEntriesType == tocEntryType.FieldSet) && !selectedItemInLinks) || itemSelected?.id == parentLink.id ||isDragging)">
{{parentLink.subEntries?.length}} {{parentLink.subEntries?.length}}
</span> </span>
@ -48,98 +48,108 @@
<!-- When item is not selected then show only the pages (first level) --> <!-- When item is not selected then show only the pages (first level) -->
<!-- <ng-container *ngIf="tocEntryIsChildOf(itemSelected,parentLink) || (!itemSelected && parentLink?.subEntriesType == tocEntryType.Page)"> --> <!-- <ng-container *ngIf="tocEntryIsChildOf(itemSelected,parentLink) || (!itemSelected && parentLink?.subEntriesType == tocEntryType.Page)"> -->
<div cdkDropList class="ml-2" [ngClass]="{'border-left-active':itemSelected?.id == parentLink?.id, 'pl-1':itemSelected?.id == parentLink?.id}" <div dragula="TABLEDRAG" class="ml-2"
[hidden]="!((parentLink?.subEntriesType!= tocEntryType.FieldSet) || selectedItemInLinks || parentLink?.id === itemSelected?.id || dragHoveringOver)" [ngClass]="{'border-left-active':itemSelected?.id == parentLink?.id, 'pl-1':itemSelected?.id == parentLink?.id, 'pb-4': isDragging && (parentLink?.type!= tocEntryType.FieldSet) && (parentLink?.id != draggingItemId) }"
(cdkDropListDropped)="drop($event)" [hidden]="!((parentLink?.subEntriesType!= tocEntryType.FieldSet) || selectedItemInLinks || parentLink?.id === itemSelected?.id || dragHoveringOver ||isDragging)"
class="cdk-link-list" class="cdk-link-list"
[cdkDropListSortingDisabled]="viewOnly"
[cdkDropListConnectedTo]="[]"
(cdkDropListEntered)="hoveroverEnter()"
(cdkDropListExited)="hoveroverLeft()"
(cdkDropListDropped)="hoveroverLeft()"
[id]="parentLink.id" [id]="parentLink.id"
[cdkDropListData]="links" [ngStyle]="{'border': overContainerId === parentLink?.id? '1px solid #129D99': '', 'min-height': ((!links?.length||(links.length ==1) ) && (parentLink?.type != tocEntryType.FieldSet) && (isDragging) && (draggingItemId != parentLink?.id)) ? '3em':'inherit'}"
> >
<ng-container *ngIf="draggingItemId != parentLink?.id">
<div *ngFor="let link of links; last as isLast" <div *ngFor="let link of links; last as isLast"
[ngClass]="{'mb-3': link.type === tocEntryType.Page}" [ngClass]="{'mb-3': link.type === tocEntryType.Page}"
cdkDrag [id]="DRAGULA_ITEM_ID_PREFIX + link.id"
[cdkDragStartDelay]="50" >
> <div class="docs-link mt-0">
<div class="docs-link mt-0"> <!-- <div class="link-name"> -->
<!-- <div class="link-name"> -->
<!-- <div class="table-item row">
<!-- <div class="table-item row"> <div class="col link-info">
<div class="col link-info"> <span style="cursor: pointer;" [ngClass]="{'active': itemSelected?.id == link.id}" (click)="itemClicked(link)" >
<span style="cursor: pointer;" [ngClass]="{'active': itemSelected?.id == link.id}" (click)="itemClicked(link)" > {{link.numbering}} {{link.label? link.label : 'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate}}
{{link.numbering}} {{link.label? link.label : 'DATASET-PROFILE-EDITOR.STEPS.GENERAL-INFO.UNTITLED' | translate}} </span>
</span> </div>
</div> <div class="table-item-actions col-auto" *ngIf="!viewOnly">
<div class="table-item-actions col-auto" *ngIf="!viewOnly"> <button class="mat-button" (click)="deleteEntry(link)"><mat-icon>delete</mat-icon></button>
<button class="mat-button" (click)="deleteEntry(link)"><mat-icon>delete</mat-icon></button> </div>
</div> </div> -->
</div> -->
<!-- For dev purposes -->
<!-- For dev purposes --> <!-- <ng-container [ngSwitch]="link.type">
<!-- <ng-container [ngSwitch]="link.type"> <div *ngSwitchCase="tocEntryType.FieldSet">
<div *ngSwitchCase="tocEntryType.FieldSet"> <span style="background-color: yellow;"> Fieldset</span>
<span style="background-color: yellow;"> Fieldset</span> </div>
</div> <div *ngSwitchCase="tocEntryType.Page">
<div *ngSwitchCase="tocEntryType.Page"> <span style="background-color: lightblue;"> Page</span>
<span style="background-color: lightblue;"> Page</span> </div>
</div> <div *ngSwitchCase="tocEntryType.Section">
<div *ngSwitchCase="tocEntryType.Section"> <span style="background-color: lightgreen;"> Section</span>
<span style="background-color: lightgreen;"> Section</span> </div>
</div> </ng-container> -->
</ng-container> -->
<!-- </div> -->
<!-- <button cdkDragHandle>drab</button> -->
<div class="ml-3">
<app-dataset-profile-table-of-contents-internal-section
[links]="link.subEntries"
(itemClick)="itemClicked($event)"
(removeEntry)="deleteEntry($event)"
[parentLink]="link"
[itemSelected] = "itemSelected"
(createFooEntry)="createNewEntry($event)"
[viewOnly]="viewOnly"
(dataNeedsRefresh)="onDataNeedsRefresh()"
[dropListGroup]="dropListGroup"
[dragHoveringOver]="dragHoveringOver"
[dropListStruct]="dropListStruct"
[depth]="depth+1">
</app-dataset-profile-table-of-contents-internal-section>
<!-- <div *ngIf="links && !viewOnly && !(parentLink?.subEntriesType == tocEntryType.Page) " > -->
<ng-container *ngIf="selectedItemInLinks && (link.type != tocEntryType.Page) && isLast && (!viewOnly) &&(link.type != tocEntryType.FieldSet)">
<button class="mat-button add-new-entry" style="padding-left: 0px;" (click)="createNewEntry({childType:link.type,parent:parentLink})">
<!-- <mat-icon>add</mat-icon> -->
<ng-container [ngSwitch]="link.type">
<ng-container *ngSwitchCase="tocEntryType.Section">
Subsection +
</ng-container>
<!-- <ng-container *ngSwitchCase="tocEntryType.Page">
Section
</ng-container> -->
<ng-container *ngSwitchCase="tocEntryType.FieldSet">
Field +
</ng-container>
</ng-container>
</button>
<!-- <button (click)="showDroplists()">show droplist</button> -->
</ng-container>
<!-- </div> --> <!-- </div> -->
</div> <!-- <button cdkDragHandle>drab</button> -->
</div> <div class="ml-3">
<app-dataset-profile-table-of-contents-internal-section
[links]="link.subEntries"
(itemClick)="itemClicked($event)"
(removeEntry)="deleteEntry($event)"
[parentLink]="link"
[itemSelected] = "itemSelected"
(createFooEntry)="createNewEntry($event)"
[viewOnly]="viewOnly"
(dataNeedsRefresh)="onDataNeedsRefresh()"
[dropListGroup]="dropListGroup"
[dragHoveringOver]="dragHoveringOver"
[dropListStruct]="dropListStruct"
[depth]="depth+1"
[DRAGULA_ITEM_ID_PREFIX]="DRAGULA_ITEM_ID_PREFIX"
[overContainerId]="overContainerId"
[isDragging]="isDragging"
[draggingItemId]="draggingItemId"
[parentRootId]="parentRootId">
</app-dataset-profile-table-of-contents-internal-section>
</div> <!-- END OF LOOP-->
<ng-container *ngIf="!isDragging">
<!-- <div *ngIf="links && !viewOnly && !(parentLink?.subEntriesType == tocEntryType.Page) " > -->
<ng-container *ngIf="selectedItemInLinks && (link.type != tocEntryType.Page) && isLast && (!viewOnly) &&(link.type != tocEntryType.FieldSet)">
<button class="mat-button add-new-entry" style="padding-left: 0px;" (click)="createNewEntry({childType:link.type,parent:parentLink})">
<!-- <mat-icon>add</mat-icon> -->
<ng-container [ngSwitch]="link.type">
<ng-container *ngSwitchCase="tocEntryType.Section">
Subsection +
</ng-container>
<!-- <ng-container *ngSwitchCase="tocEntryType.Page">
Section
</ng-container> -->
<ng-container *ngSwitchCase="tocEntryType.FieldSet">
Field +
</ng-container>
</ng-container>
</button>
<!-- <button (click)="showDroplists()">show droplist</button> -->
</ng-container>
<!-- </div> -->
</ng-container>
</div>
</div>
</div> <!-- END OF LOOP-->
</ng-container>
</div> </div>
<ng-container *ngIf="!isDragging">
<!-- BUILD SUBENTRIES IF THEY DONT EXIST -- CURRENT ITEM DOES HAVE CHILDREN --> <!-- BUILD SUBENTRIES IF THEY DONT EXIST -- CURRENT ITEM DOES HAVE CHILDREN -->
<div *ngIf="(!links && parentLink.type!= tocEntryType.FieldSet) && !viewOnly &&parentLink?.id == itemSelected?.id" class="docs-link mt-0"> <div *ngIf="(!links && parentLink.type!= tocEntryType.FieldSet) && !viewOnly &&parentLink?.id == itemSelected?.id" class="docs-link mt-0">
<div class="ml-3"> <div class="ml-3">
@ -181,14 +191,16 @@
</button> </button>
</ng-container> </ng-container>
</div> --> </div> -->
<!-- </ng-container> --> <!-- </ng-container> -->
<!-- Only for the page --> <!-- Only for the page -->
<div *ngIf="parentLink?.subEntriesType == tocEntryType.Page && !viewOnly"> <div *ngIf="parentLink?.subEntriesType == tocEntryType.Page && !viewOnly">
<button class="mat-button ml-3 mt-2" (click)="createNewEntry({childType:parentLink.subEntriesType,parent:parentLink})" style="padding-left:0px"> <button class="mat-button ml-3 mt-2" (click)="createNewEntry({childType:parentLink.subEntriesType,parent:parentLink})" style="padding-left:0px">
<!-- <mat-icon>add</mat-icon> --> <!-- <mat-icon>add</mat-icon> -->
Section + Section +
</button> </button>
<!-- <button (click)="showDroplists()">show droplist</button> <!-- <button (click)="showDroplists()">show droplist</button>
<button (click)="showStructs()">structs</button> --> <button (click)="showStructs()">structs</button> -->
</div> </div>
</ng-container>

View File

@ -25,6 +25,11 @@ export class DatasetProfileTableOfContentsInternalSection extends BaseComponent
@Input() parentLink: ToCEntry; @Input() parentLink: ToCEntry;
@Input() itemSelected: ToCEntry; @Input() itemSelected: ToCEntry;
@Input() DRAGULA_ITEM_ID_PREFIX;
@Input() overContainerId: string;
@Input() isDragging;
@Input() draggingItemId: string;
@Input() parentRootId: string;
@Input() viewOnly: boolean; @Input() viewOnly: boolean;
// @Input() dropListGroup: Set<string> = new Set<string>(); // @Input() dropListGroup: Set<string> = new Set<string>();
@ -193,4 +198,35 @@ export class DatasetProfileTableOfContentsInternalSection extends BaseComponent
onDataNeedsRefresh(){ onDataNeedsRefresh(){
this.dataNeedsRefresh.emit(); this.dataNeedsRefresh.emit();
} }
get hoveringOverParent(){
if(!this.overContainerId) return false;
const child = this._findTocEntryById(this.overContainerId, this.parentLink.subEntries);
if(!child) return false;
return true;
}
private _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry{
if(!tocentries){
return null;
}
let tocEntryFound = tocentries.find(entry=>entry.id === id);
if(tocEntryFound){
return tocEntryFound;
}
for(let entry of tocentries){
const result = this._findTocEntryById(id, entry.subEntries);
if(result){
tocEntryFound = result;
break;
}
}
return tocEntryFound? tocEntryFound: null;
}
} }

View File

@ -9,11 +9,16 @@
<app-dataset-profile-table-of-contents-internal-section [links]="links" (itemClick)="itemClicked($event)" <app-dataset-profile-table-of-contents-internal-section [links]="links" (itemClick)="itemClicked($event)"
(newEntry)="addNewEntry($event)" (removeEntry)="deleteEntry($event)" (newEntry)="addNewEntry($event)" (removeEntry)="deleteEntry($event)"
(createFooEntry)="createNewEntry($event)" (createFooEntry)="createNewEntry($event)"
[parentLink]="{ subEntriesType: tocEntryType.Page, subEntries : links , id: 'foo'}" [parentLink]="{ subEntriesType: tocEntryType.Page, subEntries : links , id: ROOT_ID}"
[itemSelected]="itemSelected" [itemSelected]="itemSelected"
[viewOnly]="viewOnly" [viewOnly]="viewOnly"
(dataNeedsRefresh)="onDataNeedsRefresh()" (dataNeedsRefresh)="onDataNeedsRefresh()"
[dropListGroup]="[]" [dropListGroup]="[]"
[DRAGULA_ITEM_ID_PREFIX]="DRAGULA_ITEM_ID_PREFIX"
[overContainerId]="overcontainer"
[isDragging]="isDragging"
[draggingItemId]="draggingItemId"
[parentRootId]="ROOT_ID"
></app-dataset-profile-table-of-contents-internal-section> ></app-dataset-profile-table-of-contents-internal-section>
<!-- <span *ngFor="let link of links; let i = index" (click)="toggle(link); goToStep(link);" class="docs-link mt-0"> <!-- <span *ngFor="let link of links; let i = index" (click)="toggle(link); goToStep(link);" class="docs-link mt-0">

View File

@ -6,6 +6,8 @@ import { distinctUntilChanged } from 'rxjs/operators';
import { type } from 'os'; import { type } from 'os';
import { SimpleChanges } from '@angular/core'; import { SimpleChanges } from '@angular/core';
import { Foo, ToCEntry, ToCEntryType } from './table-of-contents-entry'; import { Foo, ToCEntry, ToCEntryType } from './table-of-contents-entry';
import { DragulaService } from 'ng2-dragula';
import { FormArray } from '@angular/forms';
export interface Link { export interface Link {
/* id of the section*/ /* id of the section*/
@ -54,15 +56,420 @@ export class DatasetProfileTableOfContents extends BaseComponent implements OnIn
@Input() isActive: boolean; @Input() isActive: boolean;
show: boolean = false; show: boolean = false;
isDragging: boolean = false;
draggingItemId: string = null;
tocEntryType = ToCEntryType; tocEntryType = ToCEntryType;
DRAGULA_ITEM_ID_PREFIX="table_item_id_";
ROOT_ID: string = "ROOT_ID";//no special meaning
constructor( constructor(
@Inject(DOCUMENT) private _document: Document) { @Inject(DOCUMENT) private _document: Document,
private dragulaService: DragulaService
) {
super(); super();
if(this.dragulaService.find('TABLEDRAG')){
this.dragulaService.destroy('TABLEDRAG');
}
const dragula = this.dragulaService.createGroup('TABLEDRAG', {
invalid: (el,handle)=>{
// const elid = el.id;
// if(!el.id) return true;
// const elementId = (el.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX,'');
// const entry = this._findTocEntryById(elementId, this.links);
// if(!entry) return true;
// if(entry.type != this.tocEntryType.FieldSet) return true;
// console.log(el);
return false;
}
});
const drake = dragula.drake;
drake.on('drop', (el, target, source,sibling)=>{
// console.log('el:', el);
// console.log('target:', target);
// console.log('source:', source);
// console.log('sibling:', el);
const elementId = (el.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX,'');
const targetId = target.id as string;
const sourceId = source.id as string;
if(!(elementId && targetId && sourceId)){
console.error('Elements not have an id');
this.dataNeedsRefresh.emit();
return;
}
const element:ToCEntry = this._findTocEntryById(elementId, this.links);
const targetContainer:ToCEntry = this._findTocEntryById(targetId , this.links);
const sourceContainer:ToCEntry = this._findTocEntryById(sourceId, this.links);
if(!(element && (targetContainer ||((element.type===ToCEntryType.Page) && (targetId === this.ROOT_ID))) && (sourceContainer||((element.type===ToCEntryType.Page) && (sourceId === this.ROOT_ID))))){
console.info('Could not find elements');
this.dataNeedsRefresh.emit();
drake.cancel(true);
return;
}
switch(element.type){
case ToCEntryType.FieldSet:
if(targetContainer.type != this.tocEntryType.Section){
console.error('Fieldsets can only be childs of section');
this.dataNeedsRefresh.emit();
return;
}
//check if target container has no sections
if((targetContainer.form.get('sections') as FormArray).length){
console.error('Fieldsets can only be childs of section thas has no section children');
this.dataNeedsRefresh.emit();
return;
}
const fieldsetForm = element.form;
const targetFieldsets = targetContainer.form.get('fieldSets') as FormArray;
const sourceFieldsets = sourceContainer.form.get('fieldSets') as FormArray;
if(!targetFieldsets){
console.error('Not target fieldsets container found');
this.dataNeedsRefresh.emit();
return;
}
let sourceOrdinal=-1;
let idx = -1;
sourceFieldsets.controls.forEach((elem,index)=>{
if(elem.get('id').value === elementId){
sourceOrdinal = elem.get('ordinal').value;
idx = index
}
});
if(sourceOrdinal>=0 && idx>=0){
sourceFieldsets.removeAt(idx);
sourceFieldsets.controls.forEach(control=>{
const ordinal = control.get('ordinal');
if(ordinal.value>= sourceOrdinal){
const updatedOrdinalVal = ordinal.value -1;
ordinal.setValue(updatedOrdinalVal);
}
});
}
let position:number = targetFieldsets.length;
if(!sibling ||!sibling.id){
console.info('No sibling Id found');
}else{
const siblingId = (sibling.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX,'');
let siblingIndex = -1;
targetFieldsets.controls.forEach((e,idx)=>{
if(e.get('id').value === siblingId){
siblingIndex = idx;
position = e.get('ordinal').value;
}
});
if(siblingIndex>=0 && (position!=targetFieldsets.length)){
targetFieldsets.controls.filter(control=> control.get('ordinal').value >= position).forEach(control=>{
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value +1;
ordinal.setValue(updatedOrdinalVal);
})
}
}
fieldsetForm.get('ordinal').setValue(position);
targetFieldsets.insert(position,fieldsetForm);
this.dataNeedsRefresh.emit();
break;
case ToCEntryType.Section:
if(targetContainer.type == ToCEntryType.Section){
if((targetContainer.form.get('fieldSets')as FormArray).length){
console.info('Target container must only contain section children');
this.dataNeedsRefresh.emit();
return;
}
const targetSections = targetContainer.form.get('sections') as FormArray;
const elementSectionForm = element.form;
const sourceSections = elementSectionForm.parent as FormArray;
if(!(targetSections && sourceSections && elementSectionForm)){
console.info('Could not load sections');
this.dataNeedsRefresh.emit();
return;
}
let idx = -1;
sourceSections.controls.forEach((section,i)=>{
if(section.get('id').value === elementId){
idx = i;
}
});
if(!(idx>=0)){
console.info('Could not find element in Parent container');
this.dataNeedsRefresh.emit();
return;
}
sourceSections.controls.filter(control=>control.get('ordinal').value >= elementSectionForm.get('ordinal').value).forEach(control=>{
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value -1;
ordinal.setValue(updatedOrdinalVal);
});
sourceSections.removeAt(idx);
let targetOrdinal = targetSections.length;
if(sibling && sibling.id){
const siblingId = sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX,'');
targetSections.controls.forEach((section,i)=>{
if(section.get('id').value === siblingId){
targetOrdinal = section.get('ordinal').value;
}
})
if(targetOrdinal!=targetSections.length){
// section.get('ordinal').setValue(i+1);
targetSections.controls.filter(control=> control.get('ordinal').value>=targetOrdinal).forEach(control=>{
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value+1;
ordinal.setValue(updatedOrdinalVal);
});
}
}else{
console.info('no siblings found');
}
elementSectionForm.get('ordinal').setValue(targetOrdinal);
targetSections.insert(targetOrdinal, elementSectionForm);
}else if(targetContainer.type === ToCEntryType.Page){
const pageId = targetContainer.form.get('id').value;
const rootform = targetContainer.form.root;
const sectionForm = element.form;
const parentSections = sectionForm.parent as FormArray;
let parentIndex = -1;
parentSections.controls.forEach((section,i )=>{
if(section.get('id').value === elementId){
parentIndex = i
}
})
if(parentIndex<0){
console.info('could not locate section in parents array');
this.dataNeedsRefresh.emit();
return;
}
//update parent sections ordinal
parentSections.controls.filter(section=>section.get('ordinal').value >= sectionForm.get('ordinal').value).forEach(section=>{
const ordinal = section.get('ordinal');
const updatedOrdinalVal = ordinal.value -1;
ordinal.setValue(updatedOrdinalVal);
})
parentSections.removeAt(parentIndex);
let position = 0;
if(targetContainer.subEntries){
position = targetContainer.subEntries.length;
}
//populate sections
const targetSectionsArray = rootform.get('sections') as FormArray;
if(sibling && sibling.id){
const siblingId= sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX, '');
let indx = -1;
targetContainer.subEntries.forEach((e,i)=>{//TOOD TO CHECK IF ORDINAL AND INDEX IS THE SAME
if(e.form.get('id').value === siblingId){
indx = i;
position = e.form.get('ordinal').value;
}
});
if(indx>=0 && position !=targetContainer.subEntries.length) {
// e.form.get('ordinal').setValue(i+1);
targetContainer.subEntries.filter(e=>e.form.get('ordinal').value >= position).forEach(e=>{
const ordinal = e.form.get('ordinal');
const updatedOrdinalVal = ordinal.value +1;
ordinal.setValue(updatedOrdinalVal);
});
}
}else{
console.info('No sibling found');
}
sectionForm.get('ordinal').setValue(position);
sectionForm.get('page').setValue(targetContainer.id);
targetSectionsArray.push(sectionForm);
}else{
console.info('Drag not support to specific container');
this.dataNeedsRefresh.emit();
return;
}
this.dataNeedsRefresh.emit();
break;
case ToCEntryType.Page:
if(targetId != this.ROOT_ID){
console.info('A page element can only be at top level');
this.dataNeedsRefresh.emit();
return;
}
const rootForm = element.form.root;
if(!rootForm){
console.info('Could not find root!')
this.dataNeedsRefresh.emit();
return;
}
const pages = rootForm.get('pages') as FormArray;
const pageForm = element.form;
let index = -1;
pages.controls.forEach((page,i)=>{
if(page.get('id').value === elementId){
index =i;
}
});
if(index<0){
console.info('Could not locate page on pages');
this.dataNeedsRefresh.emit();
return;
}
//ordinality
pages.controls.filter(page=> page.get('ordinal').value> pageForm.get('ordinal').value).forEach(page=>{
const ordinal = page.get('ordinal');
const ordinalVal = ordinal.value - 1;
ordinal.setValue(ordinalVal);
});
pages.removeAt(index);
let targetPosition = pages.length;
if(sibling){
const siblingId = sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX, '');
pages.controls.forEach((page,i)=>{
if(page.get('id').value === siblingId){
targetPosition = page.get('ordinal').value;
}
});
}
pageForm.get('ordinal').setValue(targetPosition);
//update ordinality
pages.controls.filter(page=> page.get('ordinal').value>= targetPosition).forEach(page=>{
const ordinal = page.get('ordinal');
const ordinalVal = ordinal.value +1;
ordinal.setValue(ordinalVal);
});
pages.insert(targetPosition, pageForm);
this.dataNeedsRefresh.emit();
break;
default:
console.error('Could not support moving objects for specific type of element');
this.dataNeedsRefresh.emit();
return;
}
});
drake.on('drag',(el,source)=>{
this.isDragging = true;
this.draggingItemId = (el.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX, '');
});
drake.on('over',(el, container, source)=>{
try {
this.overcontainer = container.id;
} catch (error) {
this.overcontainer = null;
}
});
drake.on('dragend',(el)=>{
this.isDragging = false;
this.draggingItemId = null;
this.overcontainer = null;
});
} }
overcontainer: string = null;
private _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry{
if(!tocentries){
return null;
}
let tocEntryFound = tocentries.find(entry=>entry.id === id);
if(tocEntryFound){
return tocEntryFound;
}
for(let entry of tocentries){
const result = this._findTocEntryById(id, entry.subEntries);
if(result){
tocEntryFound = result;
break;
}
}
return tocEntryFound? tocEntryFound: null;
}
ngOnInit(): void { ngOnInit(): void {
//emit value every 500ms //emit value every 500ms

View File

@ -35,6 +35,7 @@
/* added to the source element while its mirror is dragged */ /* added to the source element while its mirror is dragged */
.gu-transit { .gu-transit {
opacity: 0; opacity: 0;
border: 1px dashed red;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)"; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
filter: alpha(opacity=20); filter: alpha(opacity=20);
} }