JS Gantt Screenshot

Mise en place d'un menu contextuel

Générez des raccourcis et des interactions puissantes avec vos graphiques en créant un menu contextuel. Cet article propose une implémentation en quatre étapes d'un système de menu contextuel à partir de zéro dans vos graphiques ScheduleJS.

Tuesday, June 27th, 2023 - 5 minutes read

Étape 1 : Définir la structure de votre menu contextuel à l'aide de HTML 

La première étape de ce tutoriel consiste à déclarer votre menu contextuel dans la partie HTML de l'application. La meilleure façon de garder votre code propre est de créer un composant dédié au rendu du menu contextuel et à la définition de vos interactions.

<!-- The ngStyle input will handle positionning -->
<div class="demo-planning-board-context-menu"
     [class.display]="!gantt.contextMenuOverlay.isHidden"
     [ngstyle]="gantt.contextMenuOverlay.position"
     (click)="gantt.contextMenuOverlay.onHide()">
  
  <!-- (1) Set started action -->
  <div class="demo-planning-board-context-menu-action"
       (click)="gantt.contextMenuOverlay.onSetTaskStarted()">
      Set started
  </div>
  
  <!-- (2) Set completed action -->
  <div class="demo-planning-board-context-menu-action"
       (click)="gantt.contextMenuOverlay.onSetActivityCompleted()">
    Set completed
  </div>
  
  <!-- (3) Set late action -->
  <div class="demo-planning-board-context-menu-action"
       (click)="gantt.contextMenuOverlay.onSetActivityLate()">
    Set late
  </div>
  
  <!-- (4) Set priority action -->
  <div class="demo-planning-board-context-menu-action"
       (click)="gantt.contextMenuOverlay.onSetActivityPriority()">
    {{ gantt.contextMenuOverlay.activity?.isHighPriority
         ? "Remove high priority" : "Set high priority" }}
  </div>
  
</div>

Le display sera utilisée pour cacher et afficher le menu contextuel, tandis que nous mettrons à jour sa position à l'aide de CSS et la ferons passer par la classe enfant Angular [ngStyle] propriété d'entrée.

Ici, nous avons créé une mise en page contextuelle simple avec quatre actions :

  • Set started: Modifier l'état de l'activité et la définir comme commencée
  • Set completed: Définir l'activité comme terminée
  • Set late: Définir la séquence d'activités comme tardive, à partir d'une activité spécifique
  • Set priority: Définir la priorité comme élevée ou supprimer ce paramètre

Faisons maintenant un peu de CSS pour rendre le tout plus joli. Nous recommandons d'utiliser SCSS pour créer une portée pour les classes de style comme suit :

.demo-planning-board-context-menu {

    display: none;
    position: absolute;
    z-index: 1;
    background: #555555dd;
    border-radius: 5px;
    padding: 3px 0;

    &.display {
      display: flex;
      flex-direction: column;
    }

    .demo-planning-board-context-menu-action {

      color: white;
      font: 13px $demo-planning-board-font;
      padding: 0 10px;
      margin: 3px 0;

      &:hover {
        color: black;   
        filter: brightness(0.9);
        background: rgba(255, 255, 255, 0.4);
      }

      &:active {
        filter: brightness(0.8);
      }

    }

  }

Une fois cela fait, nous pouvons commencer à jouer avec Angular pour créer la logique de cet élément.

Étape 2 : Créer une abstraction de superposition

En utilisant une approche orientée objet, nous pouvons définir une abstraction qui sera le point de départ de toutes nos superpositions, de sorte que nous puissions la réutiliser pour créer des infobulles, des modales, etc.

export abstract class PlanningBoardAbstractOverlay {

  // Attributes

  isHidden: boolean = true;
  activity: PlanningBoardActivity | undefined = undefined;
  position: PlanningBoardOverlayPosition = {};

  // Constructor

  constructor(public gantt: PlanningBoardGanttChart) { }

  // Methods

  abstract onShow(pointerEvent: PointerEvent, activity: PlanningBoardActivity | undefined): void;

  onHide(): void {
    this.isHidden = true;
  }

  setOverlayElementPosition(pointerEvent: PointerEvent): void {
    const isRight = pointerEvent.x > window.innerWidth / 2;
    const isBottom = pointerEvent.y > window.innerHeight / 2;
    const marginPx = 10;
    this.position.top = isBottom ? "auto" : pointerEvent.y + marginPx + "px";
    this.position.right = isRight ? window.innerWidth - pointerEvent.x + marginPx + "px" : "auto";
    this.position.bottom = isBottom ? window.innerHeight - pointerEvent.y + marginPx + "px" : "auto";
    this.position.left = isRight ? "auto" : pointerEvent.x + marginPx + "px";
  }

}

export interface PlanningBoardOverlayPosition {
  top?: string;
  right?: string;
  bottom?: string;
  left?: string;
}

Stockons quelques propriétés qui contiendront l'état de notre abstraction de superposition actuelle :

  • Le isHidden sera utilisée pour masquer et afficher le recouvrement.
  • Le activity permet de relier notre superposition à une activité spécifique.
  • Le position définira l'endroit où la superposition doit être rendue à l'aide de notre propriété PointerEvent.

Exposer le GanttChart dans la superposition nous aidera à créer des actions et à interagir avec nos graphiques.

La superposition exposera également trois méthodes :

  • Le onShow est utilisée pour définir la stratégie d'affichage.
  • Le onHide methode.
  • Le setOverlayElementPosition mettra à jour le position propriété. 

Étape 3 : Construire la logique du menu contextuel

En utilisant notre PlanningBoardAbstractOverlay nous pouvons maintenant créer une nouvelle classe abstraite PlanningBoardContextMenuOverlay qui contiendra la logique de notre menu contextuel.

export class PlanningBoardContextMenuOverlay extends PlanningBoardAbstractOverlay {

  // Methods

  onShow(pointerEvent: PointerEvent, activity: PlanningBoardActivity): void {
    if (activity) {
      this.isHidden = false;
      this.activity = activity;
      this.setOverlayElementPosition(pointerEvent);
    } else {
      this.onHide();
    }
  }

  // Context menu actions

  onSetTaskStarted(): void {
    this.activity.progressRatio = 0.01;
    this.gantt.redraw();
  }

  onSetActivityCompleted(): void {
    this.activity.progressRatio = 1;
    this.gantt.redraw();
  }

  onSetActivityLate(): void {
    this.activity.deadline = 0;
    this.activity.successorFinishesAfterDeadline = true;
    this.gantt.redraw();
  }

  onSetActivityPriority(): void {
    this.activity.isHighPriority = !this.activity.isHighPriority;
    this.gantt.redraw();
  }

}

Concevons le onShow processus :

  • Lorsque vous ouvrez le menu avec une activité, nous enregistrons cette activité et affichons notre menu contextuel. Utilisons l'activité setOverlayElementPosition que nous avons créée dans notre classe abstraite et lui donner notre PointerEvent.
  • Si le menu contextuel est ouvert sans activité contextuelle, nous déclenchons la fonction onHide methode.

Nos quatre actions mettront à jour les données d'activité et déclencheront un redraw, letting our underlying ActivityRenderer mettre à jour les graphiques avec ces nouvelles informations.

Étape 4 : Déclencher le menu contextuel

ScheduleJS propose un large éventail de méthodes événementielles que vous pouvez enregistrer dans l'objet principal : la méthode GanttChart. Une façon simple d'organiser le code est de créer un fichier GanttChart class that extends the default GanttChart.

// Here we create our custom GanttChart class
export class PlanningBoardGanttChart extends GanttChart<PlanningBoardRow> {

  // Instantiate our PlanningBoardContextMenuOverlay class
  readonly contextMenuOverlay: PlanningBoardContextMenuOverlay = new PlanningBoardContextMenuOverlay(this);
  
  // The minimal GanttChart implementation requires the Angular injector
  constructor(injector: Injector) {
    super(undefined, injector);
  }
    
  // Event handlers [...] 
    
}

En tant que GanttChart est au cœur de ScheduleJS, sa classe est un endroit idéal pour enregistrer les moteurs de rendu par défaut, les couches du système et les gestionnaires d'événements. Notez que l'API ScheduleJS est accessible par l'intermédiaire de l'objet GanttChart avec des méthodes telles que gantt.getGraphics().

Le GanttChart propose un ensemble de méthodes surchargeables conçues pour gérer les données saisies par l'utilisateur sur les graphiques, par exemple :

  • onRowDrawingEnginePointerDown
  • onDatelinePointerMove
  • onToggleGrid

Ce que nous voulons faire ici, c'est remplacer la fonction onRowDrawingEngineContextMenu pour déclencher une logique lors de l'ouverture de notre menu contextuel. Dans un environnement de bureau, cette méthode est appelée lorsque l'utilisateur clique avec le bouton droit de la souris n'importe où sur les graphiques.

/**
  * Trigger when right-clicking on the canvas
  */
onRowDrawingEngineContextMenu(pointerEvent: PointerEvent, row: PlanningBoardRow, index: number): void {
  super.onRowDrawingEngineContextMenu(pointerEvent, row, index);
  const hoveredActivity = this._getHoveredActivity();
  if (hoveredActivity) {
    this.tooltipOverlay.onHide();
    this.contextMenuOverlay.onShow(pointerEvent, hoveredActivity);
    this._changeDetectorRef.detectChanges();
  }
}

Maintenant, lorsque l'utilisateur clique avec le bouton droit de la souris sur les graphiques, ce code se déclenche avec l'événement de pointeur sous-jacent, la ligne actuelle et l'indice de ligne. Nous pouvons extraire la ligne courante hoveredActivity en utilisant le graphics.getHoveredActivity et la transmettre à la couche onShow methode.

Le menu contextuel que nous construisons ajoutera des interactions avec les activités présentes sur le canevas. Lors d'un clic droit de l'utilisateur, nous vérifions si nous survolons une activité, et si c'est le cas, nous masquons notre infobulle et déclenchons la commande contextMenuOverlay.onShow methode.

Pour des raisons de performance, cette méthode est exécutée en dehors de la zone Angular par ScheduleJS. Nous devons donc ajouter un appel à ChangeDetectorRef.detectChanges() pour déclencher manuellement la détection des changements et mettre à jour le DOM.

Résultat final

La vidéo suivante présente les quatre nouvelles actions que nous venons de créer :

Cet exemple n'est qu'une simple implémentation d'un menu contextuel, le but étant de vous donner des idées sur la façon dont vous pouvez interagir avec vos graphiques ScheduleJS. Vous pouvez utiliser les mêmes principes pour construire un menu contextuel plus avancé, ou même intégrer une bibliothèque tierce qui accélérera le développement du menu contextuel et déclenchera l'API ScheduleJS à partir d'ici. À vous de jouer ! 

S'il vous plaît nous contacter si vous avez des défis UX/UI ou des idées pour ScheduleJS !

Plus d'articles sur la mise en œuvre

Gantt Charts

Le TOP 3 des diagrammes de Gantt JavaScript. Découvrez leurs caractéristiques, avantages et inconvénients pour choisir le meilleur outil pour votre projet.

sceenschot example appli Gantt charts

Découvrez comment ScheduleJS s'est intégré en toute transparence à Selligent CRM, améliorant ainsi l'efficacité de la planification pour les consultants d'une grande marque de produits de beauté.

JS Gantt charts example

Cet article présente l'intégration d'un composant ScheduleJS dans un tableau Ag-Grid externe, afin de démontrer la flexibilité de ScheduleJS.

Big Gantt charts

Comment synchroniser plusieurs graphiques ? Apprenez à réutiliser votre ligne de temps ScheduleJS dans plusieurs graphiques afin de les synchroniser.

Schedulejs viewer gantt

Cet article montre comment mettre en œuvre un rendu dynamique en fonction du niveau de zoom actuel des graphiques.

Gantt Organnisation

Cet article propose une mise en œuvre pas à pas d'un mécanisme d'animation utilisant l'API JavaScript Date pour vos activités.

Cet article traite d'une implémentation de websocket avec un rendu de données en temps réel en utilisant le moteur de dessin ScheduleJS.

Gantt charts traffic analytics

Cet article présente l'implémentation d'une colonne d'information personnalisée en utilisant un composant AG Grid à la place du composant par défaut.

schedulejs viewer

Cet article vous montrera comment a été construite l'architecture de l'arbre de Gantt parent-enfant dans le ScheduleJS Viewer.

Schedulejs viewer gantt

Cet article montre comment le moteur de rendu de l'activité principale du ScheduleJS Viewer a été construit à l'aide d'exemples de code.

S’abonner
Notification pour
guest
0 Commentaires
Commentaires
Show all comments
0
We would love to to have your toughts on this. Please leave a comment below!x