Holiday Notice: Support will be provided on a limited scale from December 24th, 2024, to January 2nd, 2025. Happy holidays and a wonderful New Year!


Topic: Toast Scrolls page back to top (repost after accidental delete)

C-Contract priority asked 3 years ago


Expected behavior

When a toast is called if the page is scrolled down the toast shows at the top of the visible screen as a floating element without scrolling the page.

Actual behavior

When a toast is called if the page is scrolled down the toast shows at the top of the page and force scrolls the page back to the top. One of my pages performs currently the reset scroll the page. The angular docs for toast do not scroll the page.

Resources (screenshots, code snippets etc.)

I do see anything in my CSS or global toast controls that would this behavior happen. Also nothing obvious to me in my component-specific toast calls or component-specific scss that is different between the working page and the rest.

Non-scrolling working page gif: https://media.giphy.com/media/ZyCLu7pVno3vQGjxIU/giphy.gif

scrolling not working page gif: https://media.giphy.com/media/cY4QqrrmkLQ31gmW7Q/giphy.gif

Global Toast Options:

ToastModule.forRoot({
  opacity: 1,
  timeOut: 0,
  extendedTimeOut: 0,
  progressBar: true,
  closeButton: true,
}),

Global Toast SCSS:

#toast-container > mdb-toast-component {
max-width: 600px;
width: auto;
top: 120px;  }

Broken Page HTML:

<div class="m-1">
  <!-- Breadcrumbs -->
  <dcpc-wp-manage-display-breadcrumbs
    [step]="breadcrumbStep.Step_2"
    (navigationStarted)="saveDisplayEventHandler($event)"
  ></dcpc-wp-manage-display-breadcrumbs>

  <!-- Instructions -->
  <dcpc-wp-manage-display-instructions
    [step]="breadcrumbStep.Step_2"
    (saveDisplayEvent)="saveDisplayEventHandler($event)"
  ></dcpc-wp-manage-display-instructions>

  <!-- Panel -->
  <mdb-card class="mt-3">
    <mdb-card-body>
      <form [formGroup]="formGroup" class="container-fluid">
        <div class="row justify-content-end">
          <!-- Heading -->
          <button
            type="button"
            mdbBtn
            flat="true"
            size="lg"
            mdbWavesEffect
            (click)="clearList()"
          >
            CLEAR LIST
          </button>
        </div>
        <div class="row" id="displays-full-height">
          <div class="col-4">
            <!-- Available Fields List -->
            <mdb-card>
              <mdb-card-header class="grey lighten-4">
                <a class="w-20" (click)="sortFields()">
                  Available Fields
                  <i class="fas fa-sort"></i
                ></a>
              </mdb-card-header>
              <!------------------------------- Loader ------------------------------->
              <div
                *ngIf="!listsLoaded"
                class="
                  m-0
                  p-0
                  display-fields-overflow
                  d-flex
                  align-items-center
                  justify-content-center
                "
              >
                <div
                  class="spinner-border text-primary"
                  style="width: 3rem; height: 3rem"
                  role="status"
                ></div>
              </div>
              <div *ngIf="listsLoaded">
                <!------------------------------- List -------------------------------->
                <select
                  multiple
                  class="mulit-select-list display-fields-overflow"
                  formControlName="availableFields"
                  [hidden]="!listsLoaded"
                >
                  <option
                    *ngFor="let displayField of displayFields"
                    [ngValue]="displayField"
                  >
                    {{ displayField.description }} [{{
                      displayField.naaccrItemNumber
                    }}]
                  </option>
                </select>
              </div>
            </mdb-card>
          </div>

          <!-- Arrows -->

          <div class="col-4 d-flex align-items-center justify-content-center">
            <div class="mt-4">
              <!-- Set as critical -->
              <div class="row">
                <a class="border" (click)="addCriticalFields()">
                  <i class="fas fa-arrow-right fa-3x m-1"></i>
                </a>
              </div>
              <!-- Set as available -->
              <div class="row mt-2">
                <a class="border" (click)="removeCriticalFields()">
                  <i class="fas fa-arrow-left fa-3x m-1"></i>
                </a>
              </div>
            </div>
          </div>

          <div class="col-4">
            <mdb-card>
              <mdb-card-header class="grey lighten-4">
                <a class="w-20" (click)="sortCriticalFields()">
                  Critical Fields
                  <i class="fas fa-sort"></i
                ></a>
              </mdb-card-header>
              <!------------------------------- Loader ------------------------------->
              <div
                *ngIf="!listsLoaded"
                class="
                  m-0
                  p-0
                  display-fields-overflow
                  d-flex
                  align-items-center
                  justify-content-center
                "
              >
                <div
                  class="spinner-border text-primary"
                  style="width: 3rem; height: 3rem"
                  role="status"
                ></div>
              </div>
              <div *ngIf="listsLoaded">
                <!------------------------------- List -------------------------------->
                <select
                  multiple
                  class="mulit-select-list display-fields-overflow"
                  formControlName="criticalFields"
                  [hidden]="!listsLoaded"
                >
                  <option
                    *ngFor="let displayField of criticalFields"
                    [ngValue]="displayField"
                  >
                    {{ displayField.description }} [{{
                      displayField.naaccrItemNumber
                    }}]
                  </option>
                </select>
              </div>
            </mdb-card>
          </div>
        </div>
      </form>
    </mdb-card-body>
  </mdb-card>

  <!-- Option buttons-->
  <div class="mt-2 p-0">
    <button
      type="button"
      mdbBtn
      color="primary"
      outline="true"
      mdbWavesEffect
      class="ml-0"
      (click)="back()"
    >
      Back
    </button>
    <button
      type="button"
      mdbBtn
      color="primary"
      outline="true"
      mdbWavesEffect
      (click)="displayNavigation.navigateTo(breadcrumbStep.DisplayList)"
    >
      Cancel
    </button>
    <button
      type="button"
      mdbBtn
      color="primary"
      mdbWavesEffect
      class="float-right mr-0"
      (click)="continue()"
    >
      Continue
    </button>
  </div>
</div>

Broken Page TS:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription, Observable, of } from 'rxjs';
import { HttpDisplayService } from 'src/app/features/webplus/services/http/http-display.service';
import { BreadCrumbStep } from 'src/app/features/webplus/components/manage-display-types/manage-display-breadcrumbs/breadcrumbs.enum';
import { ManageDisplaysNavigationService } from 'src/app/features/webplus/services/data/manage-displays-navigation.service';
import {
  MDBModalService,
  MDBModalRef,
  ToastService,
} from 'ng-uikit-pro-standard';
import { Display, DisplayField } from 'src/app/shared/models';
import {
  ConfirmationModalComponent,
  ConfirmationModalData,
  DialogType,
} from 'src/app/shared/components/confirmation-modal/confirmation-modal.component';
import { SaveDisplayTypeEvent } from '../manage-display-instructions/manage-display-instructions.component';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'dcpc-wp-select-critical-fields',
  templateUrl: './select-critical-fields.component.html',
  styleUrls: ['./select-critical-fields.component.scss'],
})
export class SelectCriticalFieldsComponent implements OnInit {
  private subscription: Subscription = new Subscription();

  formGroup: FormGroup;
  breadcrumbStep = BreadCrumbStep;
  activeDisplayType: Display;
  originalDisplayFields: DisplayField[]; //all fields selected in step 1 - are available to pick from
  displayFields: DisplayField[]; //available fields in box 1- this is a changing list; fields will be removed as they are added to critical
  displayFieldsSortAscending: boolean = false;
  criticalFields: DisplayField[]; //critical fields only
  criticalFieldsSortAscending: boolean = false;
  listsLoaded: boolean = false;
  modalRef: MDBModalRef;

  constructor(
    private route: ActivatedRoute,
    public displayTypeService: HttpDisplayService,
    public displayNavigation: ManageDisplaysNavigationService,
    private modalService: MDBModalService,
    private toastService: ToastService,
    private readonly fb: FormBuilder
  ) {}

  /***********************************************************************************************************************
   ***********************************************************************************************************************
   *                                                   Initializers
   ***********************************************************************************************************************
   ***********************************************************************************************************************/

  ngOnInit(): void {
    this.displayFields = [];
    this.criticalFields = [];

    this.formGroup = this.fb.group({
      availableFields: [],
      criticalFields: [],
    });

    this.getDisplayTypeFromLocalStorage();
  }

  getDisplayTypeFromLocalStorage(): void {
    try {
      this.activeDisplayType = JSON.parse(
        localStorage.getItem('activeDisplayType')
      );

      this.originalDisplayFields = [
        ...this.activeDisplayType.displayFields.filter(
          (x) => x.description != null
        ),
      ];
      this.displayFields = [...this.originalDisplayFields];

      this.setCriticalFieldsList(this.criticalFields, true);
      this.sortFields();
      this.sortCriticalFields();

      this.listsLoaded = true;
    } catch (err) {
      console.log(err);
    }
  }

  /***********************************************************************************************************************
   ***********************************************************************************************************************
   *                                         Selected Items Manipulation
   ***********************************************************************************************************************
   ***********************************************************************************************************************/

  setCriticalFieldsList(list: DisplayField[], critical?: boolean) {
    this.displayFields.forEach((field) => {
      if (field.isCritical === critical) {
        this.criticalFields.push(field);
      }
    });

    //remove critical fields from available
    this.criticalFields.forEach((field) => {
      let fieldIndex: number = this.displayFields.indexOf(field);
      if (fieldIndex >= 0) {
        this.displayFields.splice(fieldIndex, 1);
      }
    });
  }

  /***********************************************************************************************************************
   ***********************************************************************************************************************
   *                            Add/remove sections and naaccr items from fields list
   ***********************************************************************************************************************
   ***********************************************************************************************************************/
  addCriticalFields() {
    const selectedFields = <any[]>this.formGroup.get('availableFields').value;
    this.displayFields = this.moveFields(
      selectedFields,
      this.displayFields,
      this.criticalFields,
      this.getInsertAtIndex()
    );
    this.formGroup.get('availableFields').setValue([]);
    this.formGroup.get('criticalFields').setValue(selectedFields);
  }

  removeCriticalFields() {
    const selectedFields = <any[]>this.formGroup.get('criticalFields').value;
    this.criticalFields = this.moveFields(
      selectedFields,
      this.criticalFields,
      this.displayFields
    );
    this.sortFields(false);
    this.formGroup.get('criticalFields').setValue([]);
    this.formGroup.get('availableFields').setValue(selectedFields);
  }

  clearList() {
    this.modalRef = this.modalService.show(ConfirmationModalComponent, {
      data: {
        modalData: <ConfirmationModalData>{
          headerText: 'Clear List',
          bodyText: `Do you want to remove all the Critical fields?`,
          confirmButtonText: 'REMOVE',
          cancelButtonText: 'CANCEL',
          dialogType: DialogType.Warning,
        },
      },
    });

    this.subscription.add(
      this.modalRef.content.action.subscribe((_) => {
        //only when confirmed
        this.criticalFields = [];
        this.displayFields = [...this.originalDisplayFields];
        this.sortFields(false);

        this.modalRef.hide();
        this.toastService.success('All Critical fields removed');
      })
    );
  }

  /***********************************************************************************************************************
   ***********************************************************************************************************************
   *                                                   Saving Data
   ***********************************************************************************************************************
   ***********************************************************************************************************************/

  saveDisplayEventHandler(event: SaveDisplayTypeEvent) {
    this.subscription.add(
      this.saveStepToServer(event.persistToDatabase).subscribe((response) => {
        if (event.showSaveToast) {
          // provide feedback on the operation
          this.toastService.success(
            `Display "${this.activeDisplayType.name}" has been saved!`
          );
        }
        if (event.postSaveAction) {
          event.postSaveAction();
        }
      })
    );
  }

  updateCriticalFlagsInFieldList() {
    this.originalDisplayFields.forEach((selectedField) => {
      var index = this.criticalFields.findIndex((element) => {
        return (
          element.dataDictionaryItemId === selectedField.dataDictionaryItemId
        );
      });

      if (index >= 0) {
        selectedField.isCritical = true;
      } else {
        selectedField.isCritical = false;
      }
    });

    //now update displayFields in active display type to save
    this.activeDisplayType.displayFields = this.originalDisplayFields;
  }

  saveToLocal() {
    this.updateCriticalFlagsInFieldList();
    localStorage.setItem(
      'activeDisplayType',
      JSON.stringify(this.activeDisplayType)
    );
  }

  //called by continue/previous button to send current selection to server
  saveStepToServer(persistToDatabase: boolean = true): Observable<Display> {
    this.saveToLocal();

    this.activeDisplayType = JSON.parse(
      localStorage.getItem('activeDisplayType')
    );

    if (persistToDatabase) {
      return this.displayTypeService.updateDisplayType(
        this.activeDisplayType.id,
        this.activeDisplayType
      );
    } else {
      return of(this.activeDisplayType);
    }
  }

  //Currently not used but leave here in case we want the Save button to
  //Save changes from all steps (accumulated in local storage)
  saveAllToServer(): Observable<Display> {
    this.saveToLocal();
    this.activeDisplayType = JSON.parse(
      localStorage.getItem('activeDisplayType')
    );

    //now save to server
    return this.displayTypeService.updateDisplayType(
      this.activeDisplayType.id,
      this.activeDisplayType
    );
  }

  /***********************************************************************************************************************
   ***********************************************************************************************************************
   *                                                   Sorters
   ***********************************************************************************************************************
   ***********************************************************************************************************************/

  sortFields(changeSortDirection: boolean = true) {
    if (changeSortDirection) {
      this.displayFieldsSortAscending = !this.displayFieldsSortAscending;
    }
    this.displayFields = [
      ...this.sortFieldsList(
        this.displayFields,
        this.displayFieldsSortAscending
      ),
    ];
  }

  sortCriticalFields(changeSortDirection: boolean = true) {
    if (changeSortDirection) {
      this.criticalFieldsSortAscending = !this.criticalFieldsSortAscending;
    }
    this.criticalFields = [
      ...this.sortFieldsList(
        this.criticalFields,
        this.criticalFieldsSortAscending
      ),
    ];
  }

  sortFieldsList(list: DisplayField[], ascending: boolean) {
    return list.sort((a, b) =>
      ascending
        ? // sort ascending
          a.description.toLowerCase() < b.description.toLowerCase()
          ? -1
          : a.description.toLowerCase() > b.description.toLowerCase()
          ? 1
          : 0
        : // sort descending
        b.description.toLowerCase() < a.description.toLowerCase()
        ? -1
        : b.description.toLowerCase() > a.description.toLowerCase()
        ? 1
        : 0
    );
  }

  /***********************************************************************************************************************
   ***********************************************************************************************************************
   *                                              List Helpers
   ***********************************************************************************************************************
   ***********************************************************************************************************************/

  getInsertAtIndex() {
    const selectedDisplayFiled = <any[]>(
      this.formGroup.get('criticalFields').value
    );
    let insertIndex = 0;
    if (selectedDisplayFiled && selectedDisplayFiled.length === 1) {
      insertIndex = this.displayFields.findIndex((element) => {
        return element.id === selectedDisplayFiled[0].id;
      });
    }
    return insertIndex + 1;
  }

  moveFields(
    selectedFields: any[],
    fromList: any[],
    toList: any[],
    toIndex: number = 0
  ): any[] {
    if (!(selectedFields && selectedFields.length)) {
      return fromList;
    }
    // Add selected item(s) to the top of the new list
    toList.splice(toIndex, 0, ...selectedFields);
    // Remove selected items from the original list
    return fromList.filter((item) => !selectedFields.includes(item));
  }

  /***********************************************************************************************************************
   ***********************************************************************************************************************
   *                                              Navigation
   ***********************************************************************************************************************
   ***********************************************************************************************************************/
  continue() {
    this.saveToLocal();
    this.subscription.add(
      this.saveStepToServer().subscribe((response) => {
        // provide feedback on the operation
        this.toastService.success(
          `Display "${this.activeDisplayType.name}" has been saved!`
        );
        this.displayNavigation.navigateTo(this.breadcrumbStep.Step_3);
      })
    );
  }

  back() {
    this.saveToLocal();
    this.subscription.add(
      this.saveStepToServer().subscribe((response) => {
        // provide feedback on the operation
        this.toastService.success(
          `Display "${this.activeDisplayType.name}" has been saved!`
        );
        this.displayNavigation.navigateTo(this.breadcrumbStep.Step_1);
      })
    );
  }

  /***********************************************************************************************************************
   ***********************************************************************************************************************
   *                                              Destroyers
   ***********************************************************************************************************************
   ***********************************************************************************************************************/

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

C-Contract priority commented 3 years ago

The last post I had posted yesterday I accidentally deleted. That post had a response asking for a page's HTML and ts code so I have added those to this report.


Arkadiusz Idzikowski staff commented 3 years ago

@Zachary Bell Could you please prepare a simple example that we will be able to test on our end? The code you added contains many references to variables/services that we do not have access to. It would take a long time to adjust this and we still won't be sure if we recreated the view from your application correctly.


C-Contract priority commented 3 years ago

No I can't because any simple page I create works correctly. I have tried a few times and I always works correctly where as almost no other page in our site works correctly


Arkadiusz Idzikowski staff commented 3 years ago

@Zachary Bell I added that to our to-do list and we will try to reproduce the problem on our end. However, I'm afraid it won't be easy to recreate this problem if it only occurs in a configuration specific to your application.



Please insert min. 20 characters.

FREE CONSULTATION

Hire our experts to build a dedicated project. We'll analyze your business requirements, for free.

Status

Opened

Specification of the issue

  • ForumUser: Priority
  • Premium support: Yes
  • Technology: MDB Angular
  • MDB Version: MDB4 9.4.0
  • Device: desktop
  • Browser: firefox
  • OS: windows 10
  • Provided sample code: No
  • Provided link: Yes