· 5 years ago · Sep 23, 2020, 04:42 PM
1import { Component, OnInit, ElementRef, OnDestroy, ChangeDetectorRef, ViewChild, EventEmitter, AfterContentChecked, Input } from '@angular/core';
2import { ActivatedRoute } from '@angular/router';
3import { AppContextService } from '@primavera/services';
4import { FormlyFormBuilder } from '../../../../../ng-formly/core/src/services/formly.form.builder';
5
6import { FieldType } from '../../../../../ng-formly/core/src/templates/field.type';
7import { FormDetailArray, FormLineGroup, MasterFormGroup } from '../../../../../models/forms/model';
8import { DropDownMenuItem } from '../../../../../components/dropDownMenu/dropdownmenu.component';
9import { I18nService } from '@primavera/localization';
10import { BroadcastService } from "@primavera/services";
11import { HotkeysService, HotkeysKeys } from '../../../../../services/hotkeys.service';
12
13import { getFormFieldValues, isMobileDevice } from '../../../utils';
14
15import * as _ from 'lodash';
16import { ModalService } from '@primavera/components';
17import { GridContextDirective } from '../../../../../directives/grid.directive';
18import { OnInitializeFromDetailPageModel } from '../../../../../models/forms/on-initialize-form-detail-page.model';
19import { GetLinesEventModel } from '../../../../../models/events/get-lines-event.model';
20import { DomService, DomFindOperation } from '@primavera/services';
21import { Subject, Subscription, ReplaySubject } from 'rxjs';
22import { FormlyFieldConfig } from "../../../../../ng-formly/core/src/components/formly.field.config";
23import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
24import { OperationTypeEnum } from '../../../../../components/forms/utils';
25import { IPageableDetail } from '../../../../../interfaces/pageable-detail.interface';
26
27@Component({
28 selector: 'formly-detail-section',
29 templateUrl: './detail.html'
30})
31
32export class DetailComponent extends FieldType implements OnInit, OnDestroy, AfterContentChecked {
33
34 protected static readonly NO_LINE_SELECTED_VALUE: number = -1;
35 protected static readonly NO_PENDING_ACTION_LINE_INDEX_VALUE: number = -1;
36 protected static readonly MIN_VISIBLE_LINES: number = 10;
37 protected static readonly MAX_COUNT_PAGES: number = 100000;
38
39 isFromPicking = false;
40
41 hideDropDownMenuEvent: EventEmitter<void>;
42 formControl: FormDetailArray;
43 _fields = [];
44 _emptyLines = [];
45 _visibleLines: number;
46 visibleLinesInit: number;
47 visibleLinesPage: number;
48 pageable: IPageableDetail[];
49 visibleLinesClass: string = '';
50 draftId: any;
51 isGrid: boolean = false;
52 isManyToManyGrid: boolean = false;
53
54 selectedLine: number;
55 indexOfLineWithPendingAction: number;
56 loading: boolean = false;
57
58 searchTerm: string = '';
59 filterSelectedValue: boolean = false;
60
61 pageReaded: Array<number>;
62 initArray: boolean;
63 onInitializeFromDetailPageModel: OnInitializeFromDetailPageModel;
64 _pageCount: number;
65
66 _timeout: any;
67
68 indexVisibleStart: number = 0;
69 gridVisibleStart: number = 1;
70 indexVisibleEnd: number = 10;
71 gridVisibleEnd: number = 10;
72
73 headerPaddingRight: number;
74 headerOffsetTop: number;
75 selectField: any;
76
77 headerFields: FormlyFieldConfig[] = [];
78
79 @ViewChild('detail', { static: true }) detail: ElementRef;
80 @ViewChild(GridContextDirective, { static: true }) priGridContext: GridContextDirective;
81 @ViewChild('priTableHeader', { static: false }) priTableHeader: ElementRef;
82 @ViewChild('extensibilitySpan', { static: false }) priExtensibilitySpan: ElementRef;
83 @ViewChild('fakeTableContextOptions', { static: false }) fakeTableContextOptions: ElementRef;
84
85 get emptyLines() {
86 return this._emptyLines;
87 }
88
89 get fakeLinesContextOptions() {
90 return Array(this.model.length + this.emptyLines.length);
91 }
92
93 get onInitializePage(): EventEmitter<any> {
94 return this._onDetailLoaded;
95 }
96
97 protected get newFields(): FormlyFieldConfig[] {
98 return _.cloneDeep(this.field.fieldArray.fieldGroup);
99 }
100
101 private _unsubscribeHotkeys: () => void;
102 private _hasFocus: boolean = false;
103 private _onClickDetailsCallback: (e: MouseEvent) => void;
104 private deletedLine: boolean = false;
105 private searchTextChanged = new Subject<string>();
106 /** Events responsible to broadscast when the page finishes is loading */
107 private onPageFinishesLoadingEvent = new EventEmitter<void>();
108
109 private _onDetailLoaded: EventEmitter<any>;
110
111 private destroyed$: ReplaySubject<boolean> = new ReplaySubject(1);
112
113 private pickingSelectAllToggled = false;
114
115 constructor(
116 private _element: ElementRef,
117 protected builder: FormlyFormBuilder,
118 private _i18nService: I18nService,
119 private _modalService: ModalService,
120 private _hotkeysService: HotkeysService,
121 private route: ActivatedRoute,
122 private changeDetectorRef: ChangeDetectorRef,
123 private broadcastService: BroadcastService,
124 private domService: DomService,
125 private appContextService: AppContextService
126 ) {
127 super();
128
129 this.selectedLine = DetailComponent.NO_LINE_SELECTED_VALUE;
130 this.indexOfLineWithPendingAction = DetailComponent.NO_PENDING_ACTION_LINE_INDEX_VALUE;
131 this.visibleLinesInit = DetailComponent.MIN_VISIBLE_LINES;
132 this.hideDropDownMenuEvent = new EventEmitter();
133 }
134
135 ngOnInit() {
136 this.addFocusEventListenerForAllInptus();
137 this.isGrid = ((this.field.templateOptions.displayType || 'Grid') === 'Grid' || (this.field.templateOptions.displayType || 'Grid') === 'PickingGrid' || (this.field.templateOptions.displayType || 'Grid') === 'ManyToManyGrid');
138 this.isManyToManyGrid = (this.field.templateOptions.displayType || 'Grid') === 'ManyToManyGrid';
139 this.isFromPicking = (this.field.templateOptions.displayType || 'Grid') === 'PickingGrid';
140 this.visibleLinesInit = this.field.templateOptions.itemsPerPage || DetailComponent.MIN_VISIBLE_LINES;
141 this._visibleLines = this.visibleLinesInit;
142 this.indexVisibleEnd = this._visibleLines;
143
144 if (this.model) {
145 this.model.map(() => this.add());
146 }
147
148 this.route.queryParams.pipe(takeUntil(this.destroyed$)).subscribe(params => {
149 this.draftId = params['id'];
150 });
151
152 if (this.formControl instanceof FormDetailArray) {
153 this.formControl.onLoading.pipe(takeUntil(this.destroyed$)).subscribe((s: boolean) => this.onPageFinishesLoading(s));
154
155 this.formControl.onReset.pipe(takeUntil(this.destroyed$)).subscribe(s => this.onReset(s));
156
157 this.formControl.onInitializePage.pipe(takeUntil(this.destroyed$)).subscribe((onInitializeFromDetailPageModel: OnInitializeFromDetailPageModel) => {
158 this.searchTerm = "";
159 this.filterSelectedValue = false;
160
161 this.initializePage(onInitializeFromDetailPageModel);
162 });
163
164 if (this.formControl.parent instanceof MasterFormGroup) {
165 (this.formControl.parent as MasterFormGroup).onValueChanged.pipe(takeUntil(this.destroyed$)).subscribe(() => {
166 this.setPageable();
167 });
168
169 (this.formControl.parent as MasterFormGroup).valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(() => {
170 this.setPageable();
171 });
172 }
173
174 this.formControl.onNewLines.pipe(takeUntil(this.destroyed$)).subscribe(s => {
175 s.forEach(line => {
176 if (line.item1 === "delete") {
177 let firstLineModelChanges;
178
179 let modelDeleted = this.model.find(o => {
180 return o.id === line.item2;
181 });
182
183 if (modelDeleted) {
184 let index = this.model.indexOf(modelDeleted);
185 this.removeLineFromModelAndFields(index);
186
187 this.formControl.controls.splice(index, 1);
188 this.formControl.sortControlsByIndex();
189 this.formControl.updateIndex(index, -1);
190
191 if (this.deletedLine) {
192 let selectedLine = index < this._fields.length ? index : index - 1;
193 selectedLine = selectedLine < 0 ? DetailComponent.NO_LINE_SELECTED_VALUE : selectedLine;
194 this.selectedLine = selectedLine;
195
196 if (this.selectedLine !== DetailComponent.NO_LINE_SELECTED_VALUE) {
197 setTimeout(() => this.selectFirstInputFieldFromRowAtIndex(this.selectedLine), 500);
198 }
199 }
200
201 // Notify changes to context dashboard
202 this.notifyDetailPropertiesChanged(this.field.key, this.selectedLine, firstLineModelChanges);
203 }
204 }
205 });
206
207 s.forEach(line => {
208 if (line.item1 === "add") {
209 this.addNewServerLine(line.item2, line.selectNextLine);
210 }
211 });
212
213 this.deletedLine = false;
214 });
215
216 this.formControl.onAddPage.pipe(takeUntil(this.destroyed$)).subscribe(p => {
217 this.addPageToForm(p);
218 });
219
220 this.formControl.setDetailName(this.key);
221
222 this.formControl.onRefreshLineRules().pipe(takeUntil(this.destroyed$)).subscribe(p => {
223 this._fields[p].forEach(element => {
224 this.evalExpressionFirstTime(element, p);
225 });
226 });
227 }
228
229 this.visibleLinesClass = 'line' + this._visibleLines.toString();
230 this.searchTextChanged.pipe(debounceTime(300), distinctUntilChanged(), takeUntil(this.destroyed$))
231 .subscribe((searchText) => { this.filterItems(searchText); });
232
233 if (this.isFromPicking && this.to.selectAllEnabled) {
234 this.selectField = this.field.fieldArray.fieldGroup[0];
235 Object.assign(this.headerFields, this.field.fieldArray.fieldGroup.slice(1, this.field.fieldArray.fieldGroup.length));
236 } else {
237 Object.assign(this.headerFields, this.field.fieldArray.fieldGroup);
238 }
239 }
240
241 ngAfterContentChecked() {
242 if (this.priGridContext) {
243 this.headerPaddingRight = this.priGridContext.element.nativeElement.offsetWidth - this.priGridContext.element.nativeElement.clientWidth;
244 this.headerOffsetTop = this.priGridContext.element.nativeElement.offsetTop;
245 }
246
247 super.ngAfterViewChecked();
248 }
249
250 ngOnDestroy() {
251 super.ngOnDestroy();
252 this.removeHotKeys();
253 if (this._onClickDetailsCallback) {
254 document.removeEventListener('click', this._onClickDetailsCallback);
255 this._onClickDetailsCallback = null;
256 }
257 this.destroyed$.next(true);
258 this.destroyed$.complete();
259
260 this.searchTextChanged.unsubscribe();
261 }
262
263 onFocus() {
264 if (this._hasFocus) {
265 return;
266 }
267
268 this._hasFocus = true;
269
270 this.addHotKeys();
271
272 this._onClickDetailsCallback = (e: MouseEvent) => {
273 let frame = this.detail.nativeElement.getBoundingClientRect();
274
275 // clicked ouside
276 if (
277 e.clientY < frame.y ||
278 e.clientY > frame.y + frame.height ||
279 e.clientX < frame.x ||
280 e.clientX > frame.x + frame.width
281 ) {
282 this.onBlur();
283 document.removeEventListener('click', this._onClickDetailsCallback);
284 this._onClickDetailsCallback = null;
285 }
286 };
287
288 document.addEventListener('click', this._onClickDetailsCallback);
289 }
290
291 onBlur() {
292 this._hasFocus = false;
293 this.removeHotKeys();
294 }
295
296 togglePickingSelectAll(value) {
297 this.pickingSelectAllToggled = !this.pickingSelectAllToggled;
298 this.model.forEach(item => item.selected = this.pickingSelectAllToggled);
299 this.formControl.controls.forEach((item: FormLineGroup) => {
300 item.controls['selected'].setValue(this.pickingSelectAllToggled);
301 });
302 this.changeDetectorRef.detectChanges();
303 this.formControl.pickingToggleSelectAll(this.pickingSelectAllToggled);
304 }
305
306 addIf(condition) {
307
308 if (!condition || !this.canAddRows()) {
309 return false;
310 }
311
312 this.add();
313 }
314
315 addLine(canAdd: boolean = true) {
316
317 if (!this.canAddRows() || !canAdd) {
318 return;
319 }
320
321 this.add();
322 }
323
324 add(emitEvent: boolean = true, atIndex: number = -1) {
325
326 let addAbove = false;
327 let index = this._fields.length;
328
329 if (atIndex >= 0) {
330 index = atIndex;
331 addAbove = true;
332 }
333
334 this.lineSelected(index, 0, false);
335
336 const form = new FormLineGroup({});
337 form.index = index;
338
339 let newField = this.newFields;
340 let fieldList = getFormFieldValues(newField);
341
342 fieldList.forEach(f => {
343 if ('datatabstop' in f.templateOptions) {
344 f.templateOptions.datatabstop.line = index + 1;
345 }
346 });
347
348 let lineModel = {};
349
350 let isNewLine = true;
351
352 for (let j = 0; j < fieldList.length; j++) {
353 lineModel[fieldList[j].key] = undefined;
354 }
355
356 if (!this.model[index] || addAbove) {
357 this.model.splice(index, 0, lineModel);
358 } else {
359 lineModel = this.model[index];
360 isNewLine = false;
361 }
362
363 this._fields.splice(index, 0, newField);
364
365 // get all fields that are contructors
366 let contructorFields = this._fields[index]
367 .filter((value) => {
368 return value.templateOptions.isDetailConstructor;
369 });
370
371 let firstContructorField = contructorFields[0];
372
373 if (firstContructorField && isNewLine) {
374 firstContructorField.templateOptions.disabled = false;
375 }
376
377 // build model state for detail
378 let detailModelState = this.createDetailModelState(index, lineModel, this._fields[index]);
379
380 this.builder.buildForm(form, this._fields[index], this.model[index], {}, this.parentModel, detailModelState);
381
382 if (emitEvent) {
383 if (firstContructorField) {
384 if (this.formControl instanceof FormDetailArray) {
385
386 if (addAbove) {
387 this.formControl.createLineAbove(form, firstContructorField.key, index);
388 } else {
389 this.formControl.createLine(form, firstContructorField.key);
390 }
391 }
392 } else {
393 if (!addAbove) {
394 this.formControl.insertLine(index, form);
395 } else {
396
397 let f = this.formControl.controls.find((o: FormLineGroup) => {
398 return o.index === index;
399 });
400
401 this.formControl.insertAbove(index, f as FormLineGroup, form as FormLineGroup);
402 }
403 }
404
405 if (!form.parent) {
406 form.setParent(this.formControl);
407 }
408 } else {
409 if (this.formControl instanceof FormDetailArray) {
410 if (!form.parent) {
411 form.setParent(this.formControl);
412 }
413
414 this.formControl.addLine(form, this.model[index], this.key);
415 }
416 }
417
418 form.onChange.pipe(takeUntil(this.destroyed$)).subscribe(s => {
419 Object.assign(lineModel, s);
420 });
421
422 if (this._emptyLines.length > 1) {
423 this._emptyLines.pop();
424 }
425
426 setTimeout(() => {
427 this.formControl.refreshValidations();
428 }, 100);
429 }
430
431 addNewServerLine(lineModel: any, selectNextLine = true) {
432 let index = lineModel.index;
433
434 if (!selectNextLine) {
435 this.remove(index);
436 }
437
438 const form = new FormLineGroup({});
439
440 let newField = this.newFields;
441
442 this.model.splice(index, 0, lineModel);
443 this._fields.splice(index, 0, newField);
444
445 // build modelState for detail
446 let detailModelState = this.createDetailModelState(index, lineModel, this._fields[index]);
447
448 this.builder.buildForm(form, this._fields[index], this.model[index], {}, this.parentModel, detailModelState);
449
450 // get all fields that are contructors
451 let contructorFields = this._fields[index]
452 .filter((value) => {
453 return value.templateOptions.isDetailConstructor;
454 });
455
456 let firstContructorField = contructorFields[0];
457
458 if (this.formControl instanceof FormDetailArray) {
459 if (!form.parent) {
460 form.setParent(this.formControl);
461 }
462
463 // eval disabled expression
464 newField.forEach(element => {
465 this.evalExpressionFirstTime(element, index);
466 });
467
468 this.formControl.addLine(form, this.model[index], this.key);
469 }
470
471 this.formControl.sortControlsByIndex();
472
473 this.formControl.updateIndex(lineModel.index + 1, 1);
474
475 form.onChange.pipe(takeUntil(this.destroyed$)).subscribe(s => {
476 Object.assign(lineModel, s);
477 });
478
479 if (this._emptyLines.length > 1 && selectNextLine) {
480 this._emptyLines.pop();
481 }
482
483 if (this.selectedLine >= index) {
484 this.selectedLine++;
485 }
486
487 if (!selectNextLine) {
488 this.selectedLine = index;
489 }
490 }
491
492 remove(i: number) {
493 this.formControl.removeAt(i);
494 this.model.splice(i, 1);
495 this._fields.splice(i, 1);
496 this.selectedLine = DetailComponent.NO_LINE_SELECTED_VALUE;
497 }
498
499 fields(i) {
500 if (this._fields[i]) {
501 return this._fields[i];
502 }
503
504 this._fields.splice(i, 0, this.newFields);
505
506 return this._fields[i];
507 }
508
509 onTabStop(condition?: any) {
510 if (!this.to.disabled && this.selectedLine === this.model.length - 1) {
511 this.addIf(condition || true);
512 } else {
513 if (this.hasEnableControls(this.selectedLine + 1)) {
514 this.lineSelected(this.selectedLine + 1, 0, true);
515 } else {
516 this.selectedLine = this.selectedLine + 1;
517 this.onTabStop(condition);
518 }
519 }
520 }
521
522 lineSelected(index: number, fieldIndex: number, notify = true): Promise<void> {
523 return new Promise<void>((resolve) => {
524 setTimeout(() => {
525 if (this.selectedLine !== index || (this.selectedLine === index && fieldIndex === 0)) {
526 // Notify changes to context dashboard
527 if (notify) {
528 this.notifyDetailPropertiesChanged(this.field.key, index);
529 }
530
531 this.selectedLine = index;
532 let numberOfItems: number = this.model.length - 1;
533 if (numberOfItems === index) {
534 /** Element to be scrolled to the bottom.
535 * This is needed because the element added to the grid will be at the bottom of the grid */
536 let element: HTMLElement = this.domService.findElementByClassName("table-flex-body", this._element.nativeElement);
537 this.domService.scrollBottom(element);
538 }
539 this.refreshDisableFieldsForPicking();
540
541 // If is not to focus on a specific field, it will be selected (focus)
542 // the first field of the line.
543 if (this.formState.navigateToField === "undefined") {
544 // Gain focus on new field
545 setTimeout(() => {
546 let row = document.getElementById(`${this.field.fieldKey}-row-${index}`);
547 if (row) {
548
549 let field = row.children[0].children[fieldIndex];
550 if (field) {
551 let inputs = field.getElementsByTagName("input");
552 if (inputs && inputs.length > 0) {
553 let input = inputs[0];
554 if (input.type !== "checkbox") {
555 input.focus();
556 resolve();
557 return;
558 }
559 }
560 }
561 }
562 resolve();
563 });
564 } else if (this.formState.navigateToField && this.formState.navigateToField.definition) {
565 // Navigates to a specific field
566 const fieldId = this.options.formState.navigateToField.definition.fieldId;
567 const detailkey = this.options.formState.navigateToField.definition.detailkey;
568 const lineIndex = this.options.formState.navigateToField.definition.detailIndex;
569
570 // Needed to force the selected line to be render before getting and focus the input dom element.
571 setTimeout(() => {
572 const field = document.getElementById(`${detailkey.toLowerCase()}${fieldId.toLowerCase()}line${lineIndex}`);
573 if (field) {
574 const elements = field.getElementsByTagName('input');
575 if (elements && elements.length > 0) {
576 const input = elements[0];
577 this.options.formState.navigateToField = undefined;
578 input.focus();
579 }
580 }
581
582 resolve();
583 });
584 }
585 } else { resolve(); }
586 });
587 });
588
589 }
590
591 validateKeydown(event: KeyboardEvent) {
592 return event.keyCode === 9;
593 }
594
595 lineContextOptionsAvailable(): boolean {
596 return (this.formState.mode !== 'view' && !this.to.disabled && !this.field.templateOptions.updateDisabled && (this.to.allowInsertAbove || this.to.allowDelete || this.to.allowClone || this.to.aditionalActionsFields)) ||
597 (this.to.aditionalActionsFields && this.to.aditionalActionsFields.length > 0);
598 }
599
600 extensibilityDetailVisibility() {
601 this.broadcastService.emit("openExtensibityDetailLayout", {
602 name: this.to.name,
603 title: this.to.label,
604 targetEntityName: this.to.targetEntityName,
605 createOperation: this.to.extensibilityConfiguration.createOperation,
606 editOperation: this.to.extensibilityConfiguration.editOperation,
607 component: this.priExtensibilitySpan,
608 fields: this.field.fieldArray.fieldGroup
609 });
610 }
611
612 canAddMoreLines(): boolean {
613 return ((!(this.formState.draftState === 0 && !this.formState.unlockDetails) || (this.formState.mode === 'edit' && !this.field.templateOptions.readOnly)))
614 && this.formState.mode !== 'view' && this.field.templateOptions.allowInsert && !this.field.templateOptions.disabled && (!this.to.updateDisabled || this.to.updateDisabled && this.evalExpression(this.key, false, this.to.updateDisabledClaims));
615 }
616
617 canAddRows(): boolean {
618 let cnt = this.formControl.controls.find(o => {
619 if (o instanceof FormLineGroup) {
620 return !o.id;
621 }
622 });
623
624 return cnt === undefined && !this.loading;
625 }
626
627 evalExpression(name: string, defaultValue: boolean, expression?: string): boolean {
628 try {
629 if ((this.to.uiRules && this.to.entity) || expression) {
630
631 let expressionToEvaluate: string;
632
633 if (expression) {
634 expressionToEvaluate = `!(${expression})`;
635 } else {
636 let fieldRules = this.to.uiRules.getUiFieldRulesExpressionByEntityAndField(this.to.entity, "action_" + name);
637 if (fieldRules && fieldRules.disabled) {
638 expressionToEvaluate = fieldRules.disabled;
639 }
640 }
641
642 if (expressionToEvaluate) {
643 let translatedValue = expressionToEvaluate.replace(new RegExp('model', 'g'), 'this.model[' + this.selectedLine + ']');
644 translatedValue = translatedValue.replace(new RegExp('formState', 'g'), 'this.formState');
645 translatedValue = translatedValue.replace(new RegExp('parentModel', 'g'), 'this.parentModel');
646 translatedValue = translatedValue.replace(new RegExp('appContext', 'g'), 'this.appContextService');
647
648 defaultValue = eval(translatedValue);
649 return defaultValue;
650 }
651 }
652
653 return defaultValue;
654 } catch (error) {
655 return defaultValue;
656 }
657 }
658
659 evalExpressionFirstTime(field, index) {
660 if (field.expressionProperties && field.expressionProperties["templateOptions.disabled"]) {
661 field.templateOptions.disabled = field.expressionProperties["templateOptions.disabled"].expression(this.model[index], this.formState, this.parentModel, this.modelState, this.appContextService);
662 }
663 }
664
665 initDetailConfig(page) {
666 // do nothing in scroll grid
667 }
668
669 updateCurrentPageDetailConfig(indexPage) {
670 // do nothing in scroll grid
671 }
672
673 deleteCurrentPageDetailConfig() {
674 // do nothing in scroll grid
675 }
676
677 addAboveCurrentPageDetailConfig() {
678 // do nothing in scroll grid
679 }
680
681 cloneCurrentPageDetailConfig() {
682 // do nothing in scroll grid
683 }
684
685 addCurrentPageDetailConfig() {
686
687 }
688
689 addPageToForm(page: any) {
690 /*
691 "numberOfRows": 20,
692 "pageIndex": 2,
693 "pageSize": 10,
694 "pageCount": 2,
695 */
696 this.initDetailConfig(page);
697 this.onPageLoad(page.details);
698
699 let emptyLinesSize = page.pageSize;
700 if (page.pageIndex === 1) {
701 emptyLinesSize = page.details.length;
702 }
703
704 while (this._emptyLines.length > 1 && (this._emptyLines.length + emptyLinesSize) > this._visibleLines) {
705 this._emptyLines.pop();
706 }
707
708 // 1 - add to model
709 if (this.initArray) {
710 this.initArray = false;
711 this._pageCount = page.pageCount;
712
713 for (let i = 0; i < page.numberOfRows; i++) {
714 let newField = this.newFields;
715 this.model.push({ fakeObject: true });
716 this._fields.push(newField);
717 }
718 }
719
720 page.details.forEach((element, index) => {
721 let positionArray = (page.pageIndex - 1) * page.pageSize + index;
722 /** Sometimes the index of element comes wrong.
723 we make the index equals to positionArray to correct it*/
724 element.index = positionArray;
725 this.addModelLineToForm(element, positionArray);
726 });
727
728 // this.navigateToField(page.pageSize);
729
730 this.formControl.sortControlsByIndex();
731
732 if (this.onInitializeFromDetailPageModel && this.onInitializeFromDetailPageModel.gridLineIndex > -1) {
733 if (this.onInitializeFromDetailPageModel.gridLineIndex < this.model.length) {
734 setTimeout(() => {
735 /** First, we find the grid dom element*/
736 let parentElement: HTMLElement = this.domService.findElementById(this.onInitializeFromDetailPageModel.gridId, DomFindOperation.Contains);
737 /** Then, we get all lines from the grid and extract the one that are in a given index*/
738 let line: HTMLElement = this.domService.findElementsByClassName("table-flex-row-line", parentElement)[this.onInitializeFromDetailPageModel.gridLineIndex];
739 if (line) {
740 this.domService.scrollToElement(line);
741 this.lineSelected(this.onInitializeFromDetailPageModel.gridLineIndex, 0).then(() => {
742 this.onInitializeFromDetailPageModel.onGridLineLoad.emit();
743 this.onInitializeFromDetailPageModel = new OnInitializeFromDetailPageModel();
744 });
745 }
746 });
747 } else {
748 setTimeout(() => {
749 this.readPages([1]);
750
751 this.onInitializeFromDetailPageModel.onGridLineLoad.emit();
752 this.onInitializeFromDetailPageModel = new OnInitializeFromDetailPageModel();
753 });
754 }
755 }
756
757 if (!this.changeDetectorRef['destroyed']) {
758 this.changeDetectorRef.detectChanges();
759 }
760 }
761
762 addModelLineToForm(lineModel, positionArray) {
763 // add to model
764 this.model[positionArray] = lineModel;
765
766 // create line form control
767 const form = new FormLineGroup({});
768
769 // create formly fields
770
771 let newField = this._fields[positionArray];
772
773 let detailModelState = this.createDetailModelState(positionArray, lineModel, newField);
774
775 this.builder.buildForm(form, newField, lineModel, {}, this.parentModel, detailModelState);
776
777 if (!form.parent) {
778 form.setParent(this.formControl);
779 }
780
781 // eval disabled expression
782 newField.forEach(element => {
783 this.evalExpressionFirstTime(element, positionArray);
784 });
785
786 this.formControl.addLine(form, this.model[positionArray], this.key);
787 }
788 /**
789 * Responsible to initialize the details
790 * @param onInitializeFromDetailPageModel model responsible to structure the data necessary to initialize the page
791 */
792 initializePage(onInitializeFromDetailPageModel: OnInitializeFromDetailPageModel): void {
793 this.pageReaded = [];
794 this.model = [];
795 this._fields = [];
796 this._emptyLines = Array(this._visibleLines);
797 this._timeout = undefined;
798 this._pageCount = DetailComponent.MAX_COUNT_PAGES;
799 this.selectedLine = DetailComponent.NO_LINE_SELECTED_VALUE;
800 this.indexOfLineWithPendingAction = DetailComponent.NO_PENDING_ACTION_LINE_INDEX_VALUE;
801 this.initArray = true;
802 this.onInitializeFromDetailPageModel = onInitializeFromDetailPageModel;
803
804 if (this.onInitializeFromDetailPageModel && this.onInitializeFromDetailPageModel.gridLineIndex > -1) {
805 this.readPages([Math.ceil((this.onInitializeFromDetailPageModel.gridLineIndex + 1) / this._visibleLines)]);
806 } else {
807 this.readPages([1]);
808 }
809 }
810
811 readPages(pages: Array<number>) {
812 pages.forEach((page) => {
813 this.readNext(page);
814 });
815 }
816
817 readNext(pageIndex: number) {
818 if (pageIndex <= this._pageCount && this.pageReaded.indexOf(pageIndex) === -1) {
819 this.pageReaded.push(pageIndex);
820 this.formControl.getPage(pageIndex, this._visibleLines, this.filterSelectedValue, this.searchTerm);
821 } else {
822 this.updateCurrentPageDetailConfig(pageIndex);
823 }
824 }
825
826 onScroll(event: any) {
827 this.scrolling();
828 }
829
830 onMouseWheel(delta: any) {
831 if (this.priGridContext) {
832 this.priGridContext.element.nativeElement.scrollTop = this.priGridContext.element.nativeElement.scrollTop + delta.deltaY;
833 this.priGridContext.element.nativeElement.scrollLeft = this.priGridContext.element.nativeElement.scrollLeft + delta.deltaX;
834 }
835
836 this.scrolling();
837 }
838
839 /**
840 * Verifies if the errors from a given detail line should be shown to the user.
841 * @param fc Detail line formly control
842 */
843 checkDetailLineErrors(fc: FormLineGroup) {
844 if (this.options.formState.operationType === OperationTypeEnum.PICKING) {
845 return fc.controls.selected.value;
846 } else {
847 return true;
848 }
849 }
850
851 isContextOptionsAvailable(position: number) {
852 if (this.lineContextOptions(position) && this.lineContextOptions(position).length > 0) {
853 return true;
854 } else {
855 return false;
856 }
857 }
858
859 /**
860 * Delete a line from the grid
861 * @param index index of the line that will be deleted
862 */
863 protected deleteLine(index: number): void {
864
865 if (this.canAddRows() || this.model[index].id === undefined) {
866 let lineControl: FormLineGroup = this.formControl.byIndex(index) as FormLineGroup;
867
868 if (!lineControl) { return; }
869
870 this.indexOfLineWithPendingAction = index;
871 this.loading = true;
872 this.deleteFromServer(lineControl, index).then(() => {
873 this._hasFocus = false;
874 let nextFakeLineIndex: number = this.getNextFakeLinePosition(index);
875 if (nextFakeLineIndex > 0) { this.loadLine(nextFakeLineIndex); }
876 });
877 }
878 }
879
880 protected createDetailModelState(index: number, lineModel: any, fields: any): any {
881 let detailModelState = {};
882
883 Object.keys(lineModel).forEach(key => {
884 detailModelState[key] = {
885 isLoading: false
886 };
887 });
888
889 fields.forEach(element => {
890 if (Object.keys(detailModelState).indexOf(element.fieldKey) === -1) {
891 detailModelState[element.fieldKey] = {
892 isLoading: false
893 };
894 }
895 });
896
897 // add detail modelState to master modelState
898 if (this.modelState[this.field.key]) {
899 if (!this.modelState[this.field.key]['collection']) {
900 this.modelState[this.field.key]['collection'] = [];
901 }
902
903 this.modelState[this.field.key]['collection'][index] = detailModelState;
904 }
905
906 return detailModelState;
907 }
908
909 protected onReset(s: any) {
910 let l = this.formControl.length;
911
912 for (let i = 0; i < l; i++) {
913 this.formControl.removeAt(0);
914 }
915
916 this.model = [];
917
918 this._visibleLines = this.visibleLinesInit;
919 this.resetLines();
920
921 this._fields.splice(0, this._fields.length);
922 }
923
924 protected removeLineFromModelAndFields(index: number): void {
925 // remove from model and fields
926 this.model.splice(index, 1);
927
928 this._fields.splice(index, 1);
929
930 // add a new line to the empty lines
931 if (this.isGrid && (this.model.length + this._emptyLines.length < this._visibleLines)) {
932 this._emptyLines.push(1);
933 }
934 }
935
936 protected selectFirstInputFieldFromRowAtIndex(index: number) {
937 let input = this.getFirstInputAtLine(index);
938 if (input) {
939 input.select();
940 }
941 }
942
943 protected notifyDetailPropertiesChanged(key: any, index: any, firstLineModelChanges?: any) {
944 let detailChanges = {};
945 detailChanges[key] = index < 0 ? firstLineModelChanges : this.model[index];
946 this.broadcastService.emit("contextBarChanges", detailChanges);
947 }
948
949 protected onVisibleLinesChange() {
950 this._visibleLines = this.visibleLinesPage;
951
952 this.resetLines();
953
954 this.readPages([Math.ceil((this.onInitializeFromDetailPageModel.gridLineIndex + 1) / this._visibleLines)]);
955 }
956
957 /**
958 * Set the pages array
959 */
960 protected setPageable(): void {
961 let pages: IPageableDetail[];
962 let _pageable: IPageableDetail[] = [
963 { label: '5', value: 5 },
964 { label: '10', value: 10 },
965 { label: '20', value: 20 },
966 { label: '50', value: 50 }];
967
968 if (this.canAddMoreLines()) {
969 pages = _pageable;
970 } else {
971 pages = _pageable.filter((page: IPageableDetail, index) => page.value <= this.model.length || index === 0);
972
973 if (this.model.length > pages[pages.length - 1].value && this.model.length < _pageable[_pageable.length - 1].value) {
974 pages.push({
975 label: this._i18nService.get("ngcore.components.RES_DetailMenu_Pageable_All"),
976 value: this.model.length
977 });
978 }
979 }
980
981 let exist: boolean = pages.some((page: IPageableDetail) => page.value === this._visibleLines);
982
983 if (exist) {
984 this.visibleLinesPage = this._visibleLines;
985 } else {
986 this.visibleLinesPage = this._visibleLines > pages[pages.length - 1].value ? pages[pages.length - 1].value : pages[0].value;
987 }
988
989 this.pageable = pages;
990 }
991
992 private hasEnableControls(index: number): boolean {
993 let disableControl = undefined;
994
995 if (this.formControl.controls[index] && this.formControl.controls[index] instanceof FormLineGroup) {
996 disableControl = Object.keys((this.formControl.controls[index] as FormLineGroup).controls).find(o => {
997 return (this.formControl.controls[index] as FormLineGroup).controls[o].disabled !== true;
998 });
999 } else {
1000 if (index <= this.model.length - 1) {
1001 return true;
1002 }
1003 }
1004
1005 return disableControl !== undefined && disableControl.length > 0;
1006 }
1007
1008 private addLineAbove(index: number) {
1009 this.add(true, index);
1010 }
1011
1012 /**
1013 * Add focus event lister for all inputs sons of this component
1014 */
1015 private addFocusEventListenerForAllInptus(): void {
1016 this.domService.delegate(this._element.nativeElement, 'focus', "input", () => this.onFocus());
1017 }
1018
1019 private cloneLine(index: number) {
1020 let cnt = this.formControl.controls.find(o => {
1021 if (o instanceof FormLineGroup) {
1022 return o.index === index;
1023 }
1024 return false;
1025 });
1026
1027 let lineControl: FormLineGroup = cnt as FormLineGroup;
1028 this.loading = true;
1029 this.formControl
1030 .cloneLine(lineControl, this.field.key)
1031 .pipe(takeUntil(this.destroyed$))
1032 .subscribe(newLineModel => {
1033
1034 this.loading = false;
1035
1036 // line coloned with success
1037 if (newLineModel) {
1038 this.model.push(newLineModel);
1039 this.add(false);
1040
1041 // this will make the text on the first input at the new line to be selected
1042 setTimeout(() => {
1043 let newLineIndex = this.formControl.controls.length - 1;
1044 this.selectFirstInputFieldFromRowAtIndex(newLineIndex);
1045 });
1046
1047 this.cloneCurrentPageDetailConfig();
1048 }
1049 });
1050 }
1051
1052 /**
1053 * Gets the index of the next fake line taking in account a start position
1054 * @param startPosition index position of the line which the function will start to looking for the position of the fake line
1055 * @return index of the next fake position. if does not find it, returns -1
1056 */
1057 private getNextFakeLinePosition(startPosition: number): number | -1 {
1058 const index = this.model.slice(startPosition).findIndex(m => m.fakeObject);
1059 return index === -1 ? -1 : index + startPosition;
1060 }
1061
1062 /**
1063 * Delete from the draft (server) a line from the grid
1064 * @param lineControl line to be deleted
1065 * @param index index of the line that will be deleted
1066 * @return boolean promise that allows to know if the line deletion was made with sucess
1067 */
1068 private deleteFromServer(lineControl: FormLineGroup, index: number): Promise<boolean> {
1069 return new Promise<boolean>((resolve) => {
1070 this.formControl
1071 .deleteLine(lineControl, this.field.key)
1072 .pipe(takeUntil(this.destroyed$))
1073 .subscribe((res: boolean) => {
1074 if (res) {
1075 let firstLineModelChanges;
1076 if (index === 0) {
1077 firstLineModelChanges = this.model[index];
1078
1079 for (let i in firstLineModelChanges) {
1080 firstLineModelChanges[i] = "";
1081 }
1082 }
1083
1084 this.removeLineFromModelAndFields(index);
1085
1086 let selectedLine = index < this._fields.length ? index : index - 1;
1087 selectedLine = selectedLine < 0 ? DetailComponent.NO_LINE_SELECTED_VALUE : selectedLine;
1088 this.selectedLine = selectedLine;
1089
1090 if (this.selectedLine !== DetailComponent.NO_LINE_SELECTED_VALUE) {
1091 setTimeout(() => this.selectFirstInputFieldFromRowAtIndex(this.selectedLine), 500);
1092 }
1093
1094 // Notify changes to context dashboard
1095 this.notifyDetailPropertiesChanged(this.field.key, this.selectedLine, firstLineModelChanges);
1096 }
1097
1098 this.loading = false;
1099 this.indexOfLineWithPendingAction = DetailComponent.NO_PENDING_ACTION_LINE_INDEX_VALUE;
1100 this.deleteCurrentPageDetailConfig();
1101 this.deletedLine = true;
1102 this.formControl.refreshValidations();
1103 resolve(res);
1104 });
1105 });
1106
1107 }
1108
1109 private showDetailLineModalModal(detail: string, lineId: string, operation: string, viewName: string, modalSize: string, lineModel: any) {
1110 this.formControl.showDetailLineModalModal(detail, lineId, operation, viewName, modalSize, lineModel);
1111 }
1112
1113 private showLines(i: number): boolean {
1114 return i >= this.indexVisibleStart && i <= this.indexVisibleEnd && this.model[i];
1115 }
1116
1117 private lineContextOptions(index: number): DropDownMenuItem[] {
1118 let options: DropDownMenuItem[] = [];
1119 let lineControlID: string = (this.model && this.model[index] && this.model[index].id) ? this.model[index].id : undefined;
1120
1121 let cnt = this.formControl.controls.find(o => {
1122 if (o instanceof FormLineGroup) {
1123 return o.index === index;
1124 }
1125 return false;
1126 });
1127
1128 if (this.formState.mode !== 'view' && !this.to.disabled && (!this.to.updateDisabled || this.to.updateDisabled && this.evalExpression(this.key, false, this.to.updateDisabledClaims))) {
1129 let isNotMobileDevice = !isMobileDevice();
1130
1131 if (this.to.allowInsert && this.to.allowInsertAbove) {
1132 options.push({
1133 title: this._i18nService.get("ngcore.components.RES_DetailMenu_InsertButton"),
1134 iconClass: "icon-plus_A",
1135 class: isNotMobileDevice ? "details-line-context-options-shortcut-addabove" : '',
1136 isDisabled: () => {
1137 let ctrl = this.formControl.controls[index] as FormLineGroup;
1138 return this.indexOfLineWithPendingAction !== DetailComponent.NO_PENDING_ACTION_LINE_INDEX_VALUE || lineControlID === undefined || this.evalExpression("addAbove", false, this.to.allowInsertAboveExpression) || !this.canAddRows();
1139 },
1140 action: () => {
1141 this.addLineAbove(index);
1142 }
1143 });
1144 }
1145
1146 if (this.to.allowDelete) {
1147 options.push({
1148 title: this._i18nService.get("ngcore.components.RES_DetailMenu_DeleteButton"),
1149 iconClass: "icon-close_9",
1150 class: isNotMobileDevice ? "details-line-context-options-shortcut-delete" : '',
1151 isDisabled: () => {
1152 return this.indexOfLineWithPendingAction !== DetailComponent.NO_PENDING_ACTION_LINE_INDEX_VALUE || this.evalExpression("delete", false, this.to.allowDeleteExpression) || (!this.canAddRows() && this.model[index].id !== undefined);
1153 },
1154 action: () => {
1155 this.deleteLine(index);
1156 }
1157 });
1158 }
1159
1160 if (this.to.allowInsert && this.to.allowClone) {
1161 options.push({
1162 title: this._i18nService.get("ngcore.components.RES_DetailMenu_DuplicateButton"),
1163 iconClass: "icon-igual_04",
1164 class: isNotMobileDevice ? "details-line-context-options-shortcut-clone" : '',
1165 isDisabled: () => {
1166 return this.indexOfLineWithPendingAction !== DetailComponent.NO_PENDING_ACTION_LINE_INDEX_VALUE || lineControlID === undefined || this.evalExpression("clone", false, this.to.allowCloneExpression) || !this.canAddRows();
1167 },
1168 action: () => {
1169 this.cloneLine(index);
1170 }
1171 });
1172 }
1173 }
1174
1175 if (this.to.aditionalActionsFields) {
1176 for (let field of this.to.aditionalActionsFields) {
1177 options.push({
1178 title: field.label,
1179 iconClass: "icon-action_03",
1180 isDisabled: () => {
1181 return this.indexOfLineWithPendingAction !== DetailComponent.NO_PENDING_ACTION_LINE_INDEX_VALUE || lineControlID === undefined || this.evalExpression(field.name, false);
1182 },
1183 action: () => {
1184 let dataService: string;
1185
1186 if (this.formState.mode === 'view') {
1187 dataService = field.dataService;
1188 } else {
1189 dataService = field.draftDataService;
1190 }
1191
1192 let modalSize: string;
1193
1194 switch (field.modalDialogSize) {
1195 case 'Large':
1196 modalSize = 'lg';
1197 break;
1198 case 'Medium':
1199 modalSize = 'md';
1200 break;
1201 case 'Small':
1202 modalSize = 'sm';
1203 break;
1204 default:
1205 modalSize = 'lg';
1206 break;
1207 }
1208
1209 this.showDetailLineModalModal(this.key, lineControlID, dataService, field.referencedView, modalSize, this.model[index]);
1210 }
1211 });
1212 }
1213 }
1214
1215 return options;
1216 }
1217
1218 private getFirstInputAtLine(index: number): HTMLInputElement {
1219 let row = document.getElementById(`${this.field.fieldKey}-row-${index}`);
1220 if (!row) {
1221 return null;
1222 }
1223
1224 let inputs = row.getElementsByTagName('input');
1225 if (inputs && inputs.length > 0) {
1226 return inputs[0];
1227 }
1228
1229 return null;
1230 }
1231
1232 private addHotKeys() {
1233 if (this._unsubscribeHotkeys) {
1234 return;
1235 }
1236
1237 let hotkeysDefinitions = [
1238 {
1239 // Ctrl + Alt + PgUp => Next page
1240 hotkey: [HotkeysKeys.Ctrl, HotkeysKeys.Alt, HotkeysKeys.PgUp],
1241 action: () => {
1242 if (this.priGridContext) {
1243 this.priGridContext.element.nativeElement.scrollTo(0, this.priGridContext.element.nativeElement.scrollTop - 135);
1244 }
1245 }
1246 },
1247 {
1248 // Ctrl + Alt + PgDown => Previous Page
1249 hotkey: [HotkeysKeys.Ctrl, HotkeysKeys.Alt, HotkeysKeys.PgDown],
1250 action: () => {
1251 if (this.priGridContext) {
1252 this.priGridContext.element.nativeElement.scrollTo(0, this.priGridContext.element.nativeElement.scrollTop + 135);
1253 }
1254 }
1255 },
1256 {
1257 // Ctrl + Alt + D => remove line
1258 hotkey: [HotkeysKeys.Ctrl, HotkeysKeys.Alt, HotkeysKeys.D],
1259 action: () => {
1260 if (this.formState.mode !== 'view' && !this.to.disabled && !this.to.updateDisabled && this.to.allowDelete && this.selectedLine !== DetailComponent.NO_LINE_SELECTED_VALUE && !this.loading) {
1261 this.deleteLine(this.selectedLine);
1262 }
1263 }
1264 },
1265 {
1266 // Ctrl + Del => remove line (alternative)
1267 hotkey: [HotkeysKeys.Ctrl, HotkeysKeys.Del],
1268 action: () => {
1269 if (this.formState.mode !== 'view' && !this.to.disabled && !this.to.updateDisabled && this.to.allowDelete && this.selectedLine !== DetailComponent.NO_LINE_SELECTED_VALUE && !this.loading) {
1270 this.deleteLine(this.selectedLine);
1271 }
1272 }
1273 },
1274 {
1275 // Ctrl + Alt + I => Insert new line
1276 hotkey: [HotkeysKeys.Ctrl, HotkeysKeys.Alt, HotkeysKeys.I],
1277 action: () => {
1278 if (this.formState.mode !== 'view' && !this.to.disabled && !this.to.updateDisabled && this.canAddRows()) {
1279 this.add();
1280 }
1281 }
1282 },
1283 {
1284 // Ctrl + Alt + Ins => Insert new line above
1285 hotkey: [HotkeysKeys.Ctrl, HotkeysKeys.Alt, HotkeysKeys.Ins],
1286 action: () => {
1287 if (this.formState.mode !== 'view' && !this.to.disabled && !this.to.updateDisabled && this.to.allowInsertAbove && this.selectedLine !== DetailComponent.NO_LINE_SELECTED_VALUE && this.canAddRows()) {
1288 this.addLineAbove(this.selectedLine);
1289 }
1290 }
1291 },
1292 {
1293 // Ctrl + Alt + C => Copy to end
1294 hotkey: [HotkeysKeys.Ctrl, HotkeysKeys.Alt, HotkeysKeys.C],
1295 action: () => {
1296 if (this.formState.mode !== 'view' && !this.to.disabled && !this.to.updateDisabled && this.to.allowClone && this.selectedLine !== DetailComponent.NO_LINE_SELECTED_VALUE && this.canAddRows()) {
1297 this.cloneLine(this.selectedLine);
1298 }
1299 }
1300 },
1301 {
1302 // Esc
1303 hotkey: [HotkeysKeys.Esc],
1304 action: () => {
1305 if (!this.formState.loading && this.formState.mode !== 'view' && !this.to.disabled && this.selectedLine !== DetailComponent.NO_LINE_SELECTED_VALUE && !this.loading) {
1306 let input = this.getFirstInputAtLine(this.selectedLine);
1307
1308 if (input && input === document.activeElement && (!input.value || input.value.trim() === "")) {
1309 this.deleteLine(this.selectedLine);
1310 }
1311 }
1312 }
1313 }
1314 ];
1315
1316 this._unsubscribeHotkeys = this._hotkeysService.addHotkeys(hotkeysDefinitions);
1317 }
1318
1319 private removeHotKeys() {
1320 if (this._unsubscribeHotkeys) {
1321 this._unsubscribeHotkeys();
1322 this._unsubscribeHotkeys = null;
1323 }
1324 }
1325
1326 /**
1327 * Loads and adds a new line in a specific index, if exists an element to add in that position
1328 * @param index Line position to load
1329 * @return return a boolean promise that tells if the line was loaded or not
1330 */
1331 private loadLine(index: number): Promise<boolean> {
1332 return new Promise<boolean>((resolve) => {
1333 /** First subscribes to know when the getLines services finishes is work */
1334 this.formControl.onGettingLinesFromDraft.pipe(takeUntil(this.destroyed$)).subscribe((line) => {
1335 let sucess: boolean = false;
1336 if (line) {
1337 line[0].index = index;
1338 this.addModelLineToForm(line[0], index);
1339 sucess = true;
1340 }
1341 resolve(sucess);
1342 });
1343 /** Then, request to read a new line from the grid */
1344 this.formControl.getLinesFromDraft.emit(new GetLinesEventModel(index, 1));
1345 });
1346 }
1347
1348 private scrolling() {
1349 // Loading new lines only make sense for details shown in a table form.
1350 // For showing details in a card from, the following code not make sense since will try to load new items.
1351 if (this.isGrid) {
1352 if (this.fakeTableContextOptions) {
1353 if (this.priGridContext) {
1354 this.fakeTableContextOptions.nativeElement.scrollTop = this.priGridContext.element.nativeElement.scrollTop;
1355 }
1356 }
1357
1358 if (this.priGridContext && this.priTableHeader) {
1359 this.priTableHeader.nativeElement.scrollLeft = this.priGridContext.element.nativeElement.scrollLeft;
1360 }
1361
1362 this.hideDropDownMenuEvent.emit();
1363 this.broadcastService.emit("ngcore.details.close_search_dropdown");
1364 let page: number;
1365 if (this.priGridContext && this.priGridContext.element && this.priGridContext.element.nativeElement) {
1366 const element = this.priGridContext.element.nativeElement;
1367 page = Math.round(((element.scrollTop + element.clientHeight) / element.clientHeight) * 100) / 100;
1368 }
1369
1370 // if we can't calculate the page, we assume its the first one
1371 if (isNaN(page) || page <= 0) {
1372 page = 1;
1373 }
1374
1375 let visibleStart = (Math.floor((page - 1) * this._visibleLines));
1376 let visibleEnd = visibleStart + this._visibleLines;
1377
1378 let _marginGap = 3;
1379 this.indexVisibleStart = visibleStart - _marginGap;
1380 this.indexVisibleEnd = visibleEnd + _marginGap;
1381 this.gridVisibleStart = visibleStart + 1;
1382 this.gridVisibleEnd = visibleEnd;
1383
1384 clearTimeout(this._timeout);
1385
1386 this._timeout = setTimeout(() => {
1387 this._timeout = undefined;
1388 this.readPages([Math.floor(page), Math.ceil(page)]);
1389 }, 500);
1390 }
1391
1392 }
1393
1394 private navigateToField(number: number) {
1395 if (this.formState.navigateToField && this.formControl.key === this.formState.navigateToField.definition.detailkey) {
1396 if (number > this.formState.navigateToField.definition.detailIndex) {
1397 this.selectedLine = this.formState.navigateToField.definition.detailIndex;
1398
1399 setTimeout(() => {
1400 document.getElementById(this.formState.navigateToField.definition.fieldId).focus();
1401 this.formState.navigateToField = undefined;
1402 });
1403 } else {
1404 this.formState.navigateToField = undefined;
1405 }
1406 }
1407 }
1408
1409 private checkIfSelectedLineIsVisible(): boolean {
1410 return this.selectedLine !== DetailComponent.NO_LINE_SELECTED_VALUE && this.lineContextOptionsAvailable() && this.showLines(this.selectedLine);
1411 }
1412
1413 /**
1414 * Broadcasts an event that tells the page of the grid finishes is loading
1415 * @param setLoading sets the loading as visible or not
1416 */
1417 private onPageFinishesLoading(setLoading: boolean): void {
1418 this.loading = setLoading;
1419 this.onPageFinishesLoadingEvent.emit();
1420 this.refreshDisableFieldsForPicking();
1421 if (!this.changeDetectorRef['destroyed']) {
1422 this.changeDetectorRef.detectChanges();
1423 }
1424 }
1425
1426 /**
1427 * Will refresh all disable fields, in order to be enable field validation only
1428 * for selected lines (lines which the field is checked)
1429 */
1430 private refreshDisableFieldsForPicking() {
1431 if (!this.loading && this.isFromPicking) {
1432 this.formControl.controls.forEach((controlLine: FormLineGroup) => {
1433 if (!controlLine.controls["selected"].value) {
1434 Object.keys(controlLine.controls).forEach((controlName: string) => {
1435 if (controlName.toLowerCase() !== "selected") {
1436 controlLine.controls[controlName].disable({ emitEvent: false });
1437 }
1438 });
1439 }
1440 });
1441 }
1442 }
1443
1444 /**
1445 * Applies a filter to only show the selected items of a grid; used in a many-to-many grid.
1446 */
1447 private filterSelected(): void {
1448 this.filterSelectedValue = !this.filterSelectedValue;
1449 this.onInitializeFromDetailPageModel = new OnInitializeFromDetailPageModel();
1450 this.initializePage(this.onInitializeFromDetailPageModel);
1451 }
1452
1453 /**
1454 * Used to fired the event to the grid's search input text changed
1455 * @param searchInputValue the search input value
1456 */
1457 private onSearch(searchInputValue: string): void {
1458 this.searchTextChanged.next(searchInputValue);
1459 }
1460
1461 /**
1462 * Applies a filter to only show the items whose keys contains the search input value
1463 * @param searchInputValue the search input value
1464 */
1465 private filterItems(searchInputValue: string): void {
1466 this.searchTerm = searchInputValue;
1467 this.onInitializeFromDetailPageModel = new OnInitializeFromDetailPageModel();
1468 this.initializePage(this.onInitializeFromDetailPageModel);
1469 }
1470
1471 private resetLines(): void {
1472 this.indexVisibleEnd = this._visibleLines;
1473
1474 this.visibleLinesClass = 'line' + this._visibleLines.toString();
1475 this._emptyLines = Array(Math.max((this._visibleLines - this.model.length), 1));
1476
1477 this.selectedLine = DetailComponent.NO_LINE_SELECTED_VALUE;
1478
1479 if (this.priGridContext) {
1480 this.priGridContext.element.nativeElement.scrollTop = 0;
1481 this.priGridContext.element.nativeElement.scrollLeft = 0;
1482 }
1483
1484 this.scrolling();
1485 }
1486
1487 // Extensibility point - onPageLoad
1488 private onPageLoad(details: any[]) {
1489 if (this.pickingSelectAllToggled) {
1490 details.forEach(detail => detail.selected = true);
1491 }
1492 }
1493}
1494