shlomiassaf / ngx-modialog

Modal / Dialog for Angular
http://shlomiassaf.github.io/ngx-modialog
MIT License
686 stars 242 forks source link

Latest version freezes window on close #397

Open knowhoper opened 7 years ago

knowhoper commented 7 years ago

HI,

First apologies but its very hard for me to submit a PlunkR.

I have an issue with the latest version, where when a modal is closed, one area of the page receives no events.

Calling component:

import { ISimpleWorkshop } from '../types/interfaces';
import { CalendarComponent, ICalendarEvent } from '../components/calendar.component';
import { NotificationService } from '../notification-service';
import { ColorService, IColorResult } from '../services/color-service';
import { SessionService } from '../services/session.service';
import { IEvent } from '../types/IEvent';
import { IImportantDate } from '../types/IImportantDate';
import { CalendarEventFactory } from '../util/calendar-event-factory';
import { ITrainerSimple } from '../types/ITrainerSimple';
import { TrainingCalendarDataService } from './services/training-calendar.service';
import { Component, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { ImportantDateCalendarComponent } from '../components/public-holiday-calendar.component';
import { Overlay, overlayConfigFactory } from 'angular2-modal';
import { Modal, BSModalContext } from 'angular2-modal/plugins/bootstrap';
import { Router } from '@angular/router';
import { WorkshopViewModalComponent } from '../modals/workshop-modal.component';
import * as _ from 'lodash';

@Component({
  selector: 'training-calendar',
  templateUrl: 'training-calendar.template.html'
})
export class TrainingCalendarComponent implements OnInit {
  data = [];
  selected: any;
  selectedText: string;
  events: ICalendarEvent[] = [];
  holidays: IImportantDate[] = [];
  totalWorkshops: string = '_';
  date: Date = new Date();
  breadCrumbText: string = '';

  @ViewChild(ImportantDateCalendarComponent)
  calendar: ImportantDateCalendarComponent;

  private colorService = new ColorService();

  constructor(private trainingCalendarDataService: TrainingCalendarDataService,
    private sessionService: SessionService,
    private notificationService: NotificationService,
    public modal: Modal,
    overlay: Overlay,
    vcRef: ViewContainerRef,
    private router: Router) {

    this.selected = '0';  // default to current year
    this.selectedText = 'Full Year';
    let lastIndexOfSlash =  router.url.lastIndexOf('/') + 1;
    let url = router.url;
    this.breadCrumbText = router.url.substring(lastIndexOfSlash,  url.length).replace(new RegExp('-', 'g'), ' ').replace(/\b\w/g, l => l.toUpperCase());

    if (router.url.indexOf('current-half-year') > -1) {
      this.selected = '1';
    }

    if (router.url.indexOf('year-ahead') > -1) {
      this.selected = '2';
    }

    if (router.url.indexOf('two-years-ahead') > -1) {
      this.selected = '3';
    }
    this.viewSchedule();
  }

  changed(item) {
    this.selected = item.value;
  }

  showWorkshopModal(event: ICalendarEvent) {
    this.trainingCalendarDataService.getWorkshopById(event.id).subscribe((workshop) => {
     return this.modal.open(WorkshopViewModalComponent, overlayConfigFactory({ workshop: workshop }, BSModalContext));
     }, err => this.notificationService.addErrorResponse(err));
  }

  eventClick(event) {
    this.showWorkshopModal(event.data);
  }

  viewSchedule() {
    this.sessionService.loaded = false;
    this.trainingCalendarDataService.getWorkshopsByCalendarYear(this.selected).subscribe((workshops: IEvent[]) => {
      this.events = [];
      for (let workshop of workshops) {
        try {
          this.events.push(new CalendarEventFactory().getCalendarEvent(workshop));
        }
        catch (exception) {
          console.error('error occurred in parsing events', exception);
        }
      }

      this.clear();
      this.calendar.addEvents(this.events);
      this.calendar.navigateToDate(this.sessionService.start);
      this.totalWorkshops = this.events.length.toString();
      this.sessionService.loaded = true;

    });
  }

  clear() {
    this.calendar.removeAllEvents();
    this.totalWorkshops = '_';
  }

  ngOnInit() {

    let items = ['Full Year', 'Half Year', new Date().getFullYear() + 1, new Date().getFullYear() + 2];

    for (let i = 0; i < items.length; i++) {
      const item = { id: i, text: items[i] };
      this.data.push(item);
    }
  }

  getNullString(s: string): string {
    return _.isNil(s) ? '...' : s;
  }
}

Modal

import { DialogRef, ModalComponent, CloseGuard } from 'angular2-modal';
import { NotificationService } from '../notification-service';
import { SubstringPipe } from '../pipes/substring';
import { AuthService } from '../services/auth-service';
import { ISimpleWorkshop } from '../types/interfaces';
import { IWorkshopDto } from '../types/IWorkshopDto';
import { HttpClient } from '../services/http-client';
import { Headers, Response } from '@angular/http';

import * as _ from 'lodash';
import { BSModalContext } from 'angular2-modal/plugins/bootstrap/src/modal-context';

export class WorkshopModalContext extends BSModalContext {
  public workshop: IWorkshopDto;
}

@Component({
  selector: 'modal-content',
  styles: [`
        .custom-modal-container {
            padding: 15px;
        }
        .custom-modal-header {
            background-color: #219161;
            color: #fff;
            -webkit-box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
            -moz-box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
            box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
            margin-top: -15px;
            margin-bottom: 40px;
        }
    `],
  template: require('./workshop-modal.template.html')
})
export class WorkshopViewModalComponent implements CloseGuard, ModalComponent<WorkshopModalContext> {

  context: WorkshopModalContext;
  workshop: IWorkshopDto;
  combinedAddress: string = '';
  public wrongAnswer: boolean;
  public shouldUseMyClass: boolean;

  private BASE_URL: string = process.env.API_URL;

  constructor(public dialog: DialogRef<WorkshopModalContext>,
    private authService: AuthService,
    private httpClient: HttpClient,  
    private notificationService: NotificationService) {
    this.context = dialog.context;
    this.workshop = dialog.context.workshop;

    if (!_.isNil(this.workshop.venue)) {
      this.combinedAddress = `${this.workshop.venue.address1} \r\n ${this.workshop.venue.address2}`;
    }

    dialog.setCloseGuard(this);
  }

  beforeDismiss() : Promise<boolean>{
    return new Promise<boolean>(x => true);
  }

  navigateMap(): void {
    window.open(`http://maps.google.com/?q=${this.getSnakeCaseAddress(this.workshop.venue.address2)}-${this.getSnakeCaseAddress(this.workshop.venue.suburb)}`);
  }

  getSnakeCaseAddress(val: string): string {
    if (val === undefined || val == null) {
      return '';
    }

    return val.replace(' ', '-');
  }

  closeDialog(): void {
    this.dialog.close();
  }

  exportToOutlook() {
    let headers = new Headers();
    headers.append('Accept', 'application/ical');
    this.httpClient.get(this.BASE_URL + '/api/v1/workshops/' + this.workshop.id, headers).subscribe((res: Response) => {
      this.downloadFile(res);
    });
  }

  downloadFile(data: Response) {
    let payload = data.text();
    let blob = new Blob([payload], { type: 'text/calendar' });
    let url = window.URL.createObjectURL(blob);
    window.open(url, '_blank');

    setTimeout(() => this.notificationService.addInfo('Open the downloaded file to add to Outlook'), 750);
  }
}

Here is the component that freezes - other components receive events.

<div class="js-sidebar-content" onmouseover="console.log('mouse over');">
  <header class="logo hidden-sm-down">
    <img src=“Logo.pbg” width="30" />
  </header>
  <ul class="sidebar-nav">
    <li [hidden]="!trainer">
      <a class="collapsed" data-target="#sidebar-home" data-toggle="collapse" data-parent="#sidebar" data-animation="false">
        <span class="icon">
      <span class="glyphicon glyphicon-home"></span>
        </span>
        Home
        <i class="toggle fa fa-angle-down"></i>
      </a>
      <ul id="sidebar-home" class="collapse">
        <li><a [routerLink]=" ['trainer/upcoming-workshops']">My Upcoming Workshops</a></li>
        <li><a [routerLink]=" ['trainer'] ">My Workshop History</a></li>
        <li><a [routerLink]=" ['trainer/annual-leave']">Dates I'm Not Available</a></li>
        <li><a [routerLink]=" ['trainer/calendar']">My Calendar</a></li>
      </ul>
    </li>
    <li [hidden]="!scheduler">
      <a class="collapsed" data-target="#sidebar-trainer-calendar" data-toggle="collapse" data-parent="#sidebar" data-animation="false">
        <span class="icon">
            <i class="fa fa-calendar"></i>
          </span> ACME Trainer Calendar
           <i class="toggle fa fa-angle-down"></i>
      </a>
      <ul id="sidebar-trainer-calendar" class="collapse">
        <li><a [routerLink]="['training-calendar/current-year']">Current Year</a></li>
        <li><a [routerLink]="['training-calendar/current-half-year']">Current Half Year</a></li>
        <li><a [routerLink]="['training-calendar/year-ahead']">Year Ahead</a></li>
        <li><a [routerLink]="['training-calendar/two-years-ahead']">Two Years Ahead</a></li>
      </ul>
    </li>
    <li [hidden]="!scheduler">
      <a class="collapsed" data-target="#individual-calendar" data-toggle="collapse" data-parent="#sidebar" data-animation="false">
        <span class="icon">
            <i class="fa fa-calendar"></i>
          </span> Individual Calendars
           <i class="toggle fa fa-angle-down"></i>
      </a>
      <ul id="individual-calendar" class="collapse">
        <li><a [routerLink]="['individual-calendars/ACME-trainers']">ACME Trainers</a></li>
        <li><a [routerLink]="['individual-calendars/contractor-trainers']">Contractor Trainers</a></li>
        <li><a [routerLink]="['individual-calendars/requestors']">Requestors</a></li>
      </ul>
    </li>
    <li [hidden]="!scheduler">
      <a class="collapsed" data-target="#mapping" data-toggle="collapse" data-parent="#sidebar" data-animation="false">
        <span class="icon">
            <i class="fa fa-exchange"></i>
          </span> Mapping
           <i class="toggle fa fa-angle-down"></i>
      </a>
      <ul id="mapping" class="collapse">
        <li><a [routerLink]="['mapping/important-dates']">Important Dates</a></li>
        <li><a [routerLink]="['mapping/important-ACME-dates']">Important ACME Dates</a></li>
      </ul>
    </li>
    <li [hidden]="!scheduler">
      <a class="collapsed" data-target="#sidebar-scheduler" data-toggle="collapse" data-parent="#sidebar" data-animation="false">
        <span class="icon">
            <i class="glyphicon glyphicon-time"></i>
          </span> Scheduling
        <i class="toggle fa fa-angle-down"></i>
      </a>
      <ul id="sidebar-scheduler" class="collapse">
        <li><a [routerLink]="['scheduling/locations']">Locations</a></li>
        <li><a [routerLink]="['scheduling/schedule-workshop']">Schedule Workshop</a></li>
        <li><a [routerLink]="['scheduling/training-requests']">Training Requests Received</a></li>
      </ul>
    </li>
    <li>
      <a class="collapsed" data-target="#sidebar-profile" data-toggle="collapse" data-parent="#sidebar" data-animation="false">
        <span class="icon">
            <i class="fa fa-user"></i>
          </span> Profile
        <i class="toggle fa fa-angle-down"></i>
      </a>
      <ul id="sidebar-profile" class="collapse">
        <li><a [routerLink]="['profile', this.authService.authenticatedUser.id]">My Profile</a></li>
      </ul>
    </li>
    <li [hidden]="!admin">
      <a class="collapsed" data-target="#sidebar-users" data-toggle="collapse" data-parent="#sidebar" data-animation="false">
        <span class="icon">
            <i class="fa fa-user"></i>
          </span> Users
        <i class="toggle fa fa-angle-down"></i>
      </a>
      <ul id="sidebar-users" class="collapse">
        <li><a [routerLink]=" ['users/users']">All Users</a></li>
        <li><a [routerLink]=" ['users/ACME']">Stuff</a></li>
        <li [hidden]="!mylifeadmin"><a [routerLink]=" ['users/mylife']">My Life</a></li>
        <li [hidden]="!foren5medadmin"><a [routerLink]=" ['users/forensmed']">Forensmed</a></li>
      </ul>
    </li>
  </ul>
</div>```

Typescript:

```import { ADMINISTRATOR, FOREN5MEDADMIN, MYLIFEADMIN, TRAINER, SCHEDULER } from '../../types/roles';
import { Component, OnInit, ElementRef, AfterViewInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { Location } from '@angular/common';
import { AppConfig } from '../../app.config';
import { AuthService } from '../../services/auth-service';
declare var jQuery: any;

@Component({
  selector: '[sidebar]',
  templateUrl: './sidebar.template.html'
})

export class Sidebar implements OnInit {
  $el: any;
  config: any;
  router: Router;
  location: Location;
  scheduler: boolean;
  trainer: boolean;
  admin: boolean;
  hideMenu: boolean;
  showLogo: true;
  mylifeadmin: boolean;
  foren5medadmin: boolean;
  rolesLoaded: boolean;

  constructor(config: AppConfig, el: ElementRef, router: Router, location: Location, private authService: AuthService) {
    this.$el = jQuery(el.nativeElement);
    this.config = config.getConfig();
    this.router = router;
    this.location = location;
  }

  userHasRole(desiredRole): boolean {

    if (this.authService.userIsAdmin){
      return true;
    }

    let foundRole = this.authService.authenticatedUser.roles.find((r) => r.name.toLowerCase() === desiredRole.toLowerCase());
    return foundRole !== void 0 && foundRole !== null;
  }

  initSidebarScroll(): void {
    let $sidebarContent = this.$el.find('.js-sidebar-content');
    if (this.$el.find('.slimScrollDiv').length !== 0) {
      $sidebarContent.slimscroll({
        destroy: true
      });
    }
    $sidebarContent.slimscroll({
      height: window.innerHeight,
      size: '4px'
    });
  }

  changeActiveNavigationItem(location): void {
    let $newActiveLink = this.$el.find('a[href="#' + location.path() + '"]');

    // collapse .collapse only if new and old active links belong to different .collapse
    if (!$newActiveLink.is('.active > .collapse > li > a')) {
      this.$el.find('.active .active').closest('.collapse').collapse('hide');
    }
    this.$el.find('.sidebar-nav .active').removeClass('active');

    $newActiveLink.closest('li').addClass('active')
      .parents('li').addClass('active');

    // uncollapse parent
    $newActiveLink.closest('.collapse').addClass('in')
      .siblings('a[data-toggle=collapse]').removeClass('collapsed');
  }

  ngOnInit(): void {

    if (!this.authService.isLoggedIn) {
      this.router.navigateByUrl('login');
      return;
    }

    jQuery(window).on('sn:resize', this.initSidebarScroll.bind(this));
    this.initSidebarScroll();

    this.router.events.subscribe((event) => {
      if (event instanceof NavigationEnd) {
        this.changeActiveNavigationItem(this.location);
      }
    });
  }
}

Is there anything obvious I am doing wrong?

Thanks in advance

knowhoper commented 7 years ago

This appears to be caused by the sidebar being fixed;

.sidebar{
  **position: fixed;**
  left: 0;
  top: 0;
  bottom: 0;
  z-index: 0;
  width: $sidebar-width;
  background-color: $sidebar-bg-color;
  color: $sidebar-color;

  .slimScrollBar{
    @include border-radius(0 !important);
    background-color: $sidebar-color !important;
  }
}

Can anyone help?

Thanks in advance.