2

StackBlitz: https://stackblitz.com/edit/league-predictions

I have a project where I want to predict football league standings. I have two lists, one where the prediction goes, and one with all the teams.

At the beginning the prediction list is empty, so you can start dragging teams in. But because it is empty, the first team is automatically ranked first. Of course you can sort them later, but what I want is predefined slots based on the number of team. This way you can drag the teams directly in the right place.

I can't really find a solution on the internet on how to achieve this.

This is my current situation, so you can see what I am talking about: League Predictions

And this is what I want to achieve.enter image description here

Does someone know how to predefine slots for Angular CDK DragDrop

This is my current code.

<div class="container">
  <div class="example-container">
    <h5>Predictions</h5>
    
    <div
      cdkDropList
      #predictionsList="cdkDropList"
      [cdkDropListData]="predictions"
      [cdkDropListConnectedTo]="[teamList]"
      class="example-list"
      (cdkDropListDropped)="drop($event)"
    >
  
      <div class="example-box" *ngFor="let prediction of predictions; let i = index" cdkDrag>
        <app-team [team]="prediction" [index]="i"></app-team>
      </div>
  
    </div>
  </div>
  
  <div class="example-container">
    <h5>Teams</h5>
  
    <div
      cdkDropList
      #teamList="cdkDropList"
      [cdkDropListData]="teams"
      [cdkDropListConnectedTo]="[predictionsList]"
      class="example-list"
      (cdkDropListDropped)="drop($event)"
    >
  
    <div class="example-box" *ngFor="let team of teams;" cdkDrag>
      <app-team [team]="team"></app-team>
    </div>
  
    </div>
  </div>
</div>

Don't mind the long list of team, this will all be data from database

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  
  constructor() { }

  ngOnInit(): void {
  }

  predictions = [
  ];

  teams = [
    {
      name: 'ADO Den Haag',
      logo: 'Ado-Den-Haag-Logo.png'
    },
    {
      name: 'Ajax',
      logo: 'AFC-Ajax-Logo.png'
    },
    {
      name: 'AZ',
      logo: 'AZ-Alkmaar-Logo.png'
    },
    {
      name: 'FC Emmen',
      logo: 'FC-Emmen-Logo.png'
    },
    {
      name: 'FC Groningen',
      logo: 'FC-Groningen-Logo.png'
    },
    {
      name: 'FC Twente',
      logo: 'fc-twente-logo.png'
    },
    {
      name: 'FC Utrecht',
      logo: 'FC-Utrecht-Logo.png'
    },
    {
      name: 'Feyenoord',
      logo: 'Feyenoord-Rotterdam-Logo.png'
    },
    {
      name: 'Fortuna Sittard',
      logo: 'Fortuna-Sittard-Logo.png'
    },
    {
      name: 'Heracles',
      logo: 'Heracles-Almelo-Logo.png'
    },
    {
      name: 'PEC Zwolle',
      logo: 'PEC-Zwolle-Logo.png'
    },
    {
      name: 'PSV',
      logo: 'PSV-Eindhoven-Logo.png'
    },
    {
      name: 'RKC Waalwijk',
      logo: 'rkc-waalwijk.png'
    },
    {
      name: 'SC Heerenveen',
      logo: 'SC-Heerenveen-Logo.png'
    },
    {
      name: 'Sparta Rotterdam',
      logo: 'Sparta_Rotterdam_logo.png'
    },
    {
      name: 'Vitesse',
      logo: 'Vitesse-Arnhem-Logo.png'
    },
    {
      name: 'VVV Venlo',
      logo: 'VVV-Venlo-Logo.png'
    },
    {
      name: 'Willem II',
      logo: 'Willem-II-Logo.png'
    },
  ];

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(event.previousContainer.data,
                        event.container.data,
                        event.previousIndex,
                        event.currentIndex);
    }
  }

}
Marcel
  • 398
  • 2
  • 4
  • 22

2 Answers2

1

made a stackblitz outlining what to do, first, create dummy entries in your predictions array:

  getPrediction(): Team[] {
    let localStorageItem = JSON.parse(localStorage.getItem(this.league.name));
    return localStorageItem == null ? this.getTeams().map(t => ({})) : localStorageItem.standings;
  }

this will fill out your slots. disable dragging on these items:

<div class="example-box" *ngFor="let prediction of predictions; let i = index" cdkDrag [cdkDragDisabled]="!prediction.name">

next you need to add a parameter to your drop function to know which container is being dropped on:

drop(event: CdkDragDrop<Team[]>, droppedOn: 'teams' | 'predictions') {

and template updates appropriately:

(cdkDropListDropped)="drop($event, 'predictions')"

 ...

(cdkDropListDropped)="drop($event, 'teams')"

in your drop function, you use this parameter in the case of a list transfer, to either add or remove a placeholder:

  if (droppedOn === 'teams') {
    // moved back to teams, need to re add placeholder
    this.predictions.push({});
  } else {
    // otherwise, removing a placeholder
    // find the idx to remove, first placeholder at or below current idx
    let removeIdx = this.predictions.findIndex((t, i) => i >= event.currentIndex && !t.name);
    if (removeIdx < 0) {
      // or just the first available.
      removeIdx = this.predictions.findIndex(t => !t.name);
    }
    this.predictions.splice(removeIdx, 1);
  }
  transferArrayItem(event.previousContainer.data,
                    event.container.data,
                    event.previousIndex,
                    event.currentIndex);

maybe some tweaks / improvements could be made to the algorithm to determine which blank to remove in the case of dropping a team on a team or where to insert the blank on switch back to teams, but the simple version here worked well enough when i played with it.

working blitz: https://stackblitz.com/edit/league-predictions-ouu8nr?file=src%2Fapp%2Fprediction%2Fprediction.component.ts

bryan60
  • 28,215
  • 4
  • 48
  • 65
  • Like you said, there are some tweaks I can do. But this comes close to what I'm after. Thanks! – Marcel Aug 28 '20 at 16:50
-1

Well without a working stackblitz it's hard to provide any useful codesamples, but I give it a shot.

For the left list I would create an Array with empty objects for the same size as your teams list.

I would create {name: null, logo: null} entries and have a check in the template to display nothing if name === null

Extend the drop event handler and add a check if (dropTarget.name === null) and replace the dummy entry with your value. Otherwise keep your existing logic

Edit: Basic Stackblitz example: Stackblitz

Nicolas Gehlert
  • 2,626
  • 18
  • 39
  • I managed to get the StackBlitz working. https://stackblitz.com/edit/league-predictions – Marcel Aug 28 '20 at 15:03
  • updated my answer with stackblitz example. steps are pretty straightforward, just as I described in my post :) – Nicolas Gehlert Aug 28 '20 at 15:41
  • there are a few corner cases here, moving teams back to the original array, and dropping a team on a team in the predictions array, and also prevent moving placeholder items. – bryan60 Aug 28 '20 at 15:50
  • well I won't do all the work for you. This is a working proof of concept. – Nicolas Gehlert Aug 29 '20 at 16:14