Jak utworzyć wielopoziomową listę rozwijaną i powiązać z danymi pochodzącymi z serwera

15

Jestem bardzo nowy w Angular. Mam trochę pracy nad Angularem.

Muszę powiązać zagnieżdżoną listę rozwijaną dla Jsondanych pochodzących z serwera, wywołując Rest Api.

Dane mają jeden atrybut LgLevel, określa poziom w hierarchii grupy. Rodzic będzie musiał level=0, natychmiastowa Child=1, Grandchild=2i tak dalej. Childi Grandchildma ParentLocationGrouppole, które pokazuje, w którym menu nadrzędnym będzie menu podrzędne.

To są moje jsondane. Mam ogromne dane, ale nie pokazuję wszystkich.

{
"ArrayOfLocationGroup": {
  "LocationGroup": [
     {
        "Id": "628",
        "Name": "TEST1",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources"
        },
        "ParentLocationGroup": {
           "_uuid": "bdce4396-9c60-4831-90f2-6f793becb362",
           "__text": "570"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "0"
        }
     },
     {
        "Id": "630",
        "Name": "TEST2",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "PAM-TEST"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     },
     {
        "Id": "631",
        "Name": "TEST3",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "PAA-TEST"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     },
     {
        "Id": "697",
        "Name": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "TEST4"
        },
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "PAE-TEST"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     },
     {
        "Id": "700",
        "Name": "TEST5",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "cuba"
        },
        "ParentLocationGroup": {
           "_uuid": "704af4cf-9feb-4f1b-aa00-d1c7926f7901",
           "__text": "694"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "2"
        }
     },
     {
        "Id": "706",
        "Name": "TEST5",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "VOIP-Test"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     },
     {
        "Id": "718",
        "Name": "TEST7",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     },
     {
        "Id": "719",
        "Name": "TEST8",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "MEM_RS"
        },
        "ParentLocationGroup": {
           "_uuid": "52073e2b-48b5-41a9-9c2b-d793835cf285",
           "__text": "718"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "2"
        }
     },
     {
        "Id": "752",
        "Name": "TEST9",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "ELDIT"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     },
     {
        "Id": "753",
        "Name": "TEST10",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "GXYA"
        },
        "ParentLocationGroup": {
           "_uuid": "52073e2b-48b5-41a9-9c2b-d793835cf285",
           "__text": "718"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "2"
        }
     },
     {
        "Id": "760",
        "Name": "TEST11",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "STAGE2"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     },
     {
        "Id": "761",
        "Name": "TEST12",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "INIT"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     },
     {
        "Id": "762",
        "Name": "TEST13",
        "GroupId": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "USIT"
        },
        "ParentLocationGroup": {
           "_uuid": "894055b6-b132-4dc2-a12a-971ecc0c7224",
           "__text": "628"
        },
        "LgLevel": {
           "_xmlns": "http://www.air-watch.com/servicemodel/resources",
           "__text": "1"
        }
     }
  ],
  "_xmlns:xsd": "http://www.w3.org/2001/XMLSchema"
}
}

Próbowałem go opracować, ale znalazłem wszystkie przykłady bootstrapz danymi statycznymi w htmlpliku i osobnym CSSpliku, co było dla mnie skomplikowane.

Chcę to zrobić dynamicznie, używając TypeScript. Jak mogę zacząć nad tym pracować.

Arvind Chourasiya
źródło
Po pierwsze, format danych jest XMLi nie JSON. Czy możesz również dodać cokolwiek, co próbowałeś? Może bardziej szczegółowo przyjęte podejście.
vatz88
@ vatz88 - Tak, to jest xml właśnie wklejony od listonosza. Próbowałem htmlkodu, który ma statycznie zagnieżdżone listy. Spróbuję go edytować i opublikuję Jsondane. Nie spodoba ci się to, co próbowałem :)
Arvind Chourasiya
Nie musisz się martwić tym, co do tej pory zakodowałeś. Podejście byłoby takie - zakodować dane w pliku ts, w html utworzyć powiązania zgodnie z danymi, które trzeba renderować. Gdy logika będzie poprawna, pracuj nad dynamicznym uzyskiwaniem danych, a następnie niech magia z wiązaniem będzie działać.
vatz88
@ vatz88 - Mój kod statyczny był w htmlpliku. Mam pomysł, aby go uruchomić. Możesz mi pomóc
Arvind Chourasiya
@ArvindChourasiya istnieje wiele dzieci LgLevel 1, jak rozpoznać, który wnuk LgLevel 2 należy do którego dziecka?
Satish Pai

Odpowiedzi:

4

Jest to próbka zakodowana, której potrzebujesz według danych na poziomie zagnieżdżonym z danych JSON. Teraz możesz zapętlić sformatowane dane JSON w DOM przy użyciu danych modelu . Mam nadzieję, że pomoże ci to stworzyć menu rozwijane na wielu poziomach

groupBy(xs, key) {
   return xs.reduce(function (rv, x) {
     (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
   }, {});
 }

var model;

getData() {
 var   sampleData = {
  "ArrayOfLocationGroup": {
    "LocationGroup": [
      ...
      ...//Server response data
      ],
    "_xmlns:xsd": "http://www.w3.org/2001/XMLSchema"
  }
 }    

var list = this.sampleData["ArrayOfLocationGroup"]["LocationGroup"];
var formattedList = [];

list.forEach(element => {

  var obj = {  //Make sure your server response data to like this structure
    "Id": element.Id,
    "Name": element.Name,
    "GroupId": element.GroupId.__text,
    "ParentLocationGroup": element.ParentLocationGroup.__text,
    "LgLevel": element.LgLevel.__text,
    "Child" : []
  }
  formattedList.push(obj);
});

var groupDataList = this.groupBy(formattedList, "LgLevel");

var parents = groupDataList[0];
var child = groupDataList[1];
var childOfChild = groupDataList[2];

child.forEach(c => {
  c.Child = childOfChild.filter(x => x.ParentLocationGroup == c.Id);
})

parents.forEach(p => {
  p.Child = child.filter(x => x.ParentLocationGroup == p.Id);
})

this.model = parents;
}

Plik HTML

    <ul class="nav site-nav">
     <li class=flyout>
      <a href=#>Dropdown</a>
      <!-- Flyout -->
      <ul class="flyout-content nav stacked">
        <li *ngFor="let parent of model" [class.flyout-alt]="parent.Child.length > 0"><a href=#>{{parent.Name}}</a>
          <ul *ngIf="parent.Child.length > 0" class="flyout-content nav stacked">
            <li *ngFor="let c of parent.Child" [class.flyout-alt]="c.Child.length > 0"><a href=#>{{c.Name}}</a>
              <ul *ngIf="c.Child.length > 0" class="flyout-content nav stacked">
                <li *ngFor="let cc of c.Child" [class.flyout-alt]="cc.Child.length > 0"><a href=#>{{cc.Name}}</a></li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>

Zgodnie z danymi odpowiedzi serwera uporządkuj dane modelu. Zmieniono format odpowiedzi Json ( __text na #text )

 var obj = {
    "Id": element.Id,
    "Name": element.Name && element.Name.#text ? element.Name.#text : element.Name,
    "GroupId": element.GroupId && element.GroupId.#text ? element.GroupId.#text : element.GroupId,
    "ParentLocationGroup": element.ParentLocationGroup && element.ParentLocationGroup.#text ? element.ParentLocationGroup.#text : element.ParentLocationGroup,
    "LgLevel": element.LgLevel && element.LgLevel.#text ? element.LgLevel.#text : element.LgLevel,
    "Child" : []
  }
Yaseer
źródło
Czy możesz również opublikować plik .html.
Arvind Chourasiya
@ArvindChourasiya Czy potrzebujesz listy rozwijanej tagu czy tylko listy rozwijanej ( link )?
Yaseer
Potrzebuję, że pokazuje się w opcji koloru z powyższego linku.
Arvind Chourasiya
@ArvindChourasiya Zaktualizowałem post za pomocą html
Yaseer
Nie mylę się z twoim kodem. getDataNigdzie nie korzystasz . Czy możesz sprawdzić swój kod jeden i dodać otwieranie i zamykanie.
Arvind Chourasiya
4

Wygląda na to, że masz już inną odpowiedź, która spełnia Twoje wymagania. Ale to rozwiązanie zajęło mi trochę czasu. W każdym razie postanowiłem to opublikować.

Poniższy fragment kodu służy do budowy drzewiastej struktury hierarchicznych danych rodzic-dziecko:

  processData(data) {
    let locationData = data.ArrayOfLocationGroup.LocationGroup;
    let level0 = [];
    let tempMap = {};
    for (let i = 0; i < locationData.length; i++) {
      let currItem = this.getDataObject(locationData[i]);
      if (tempMap[currItem.id] == undefined) {
        tempMap[currItem.id] = currItem;
        if (tempMap[currItem.parentLocationGroup] == undefined) {
          tempMap[currItem.parentLocationGroup] = { children: [] };
        }
        tempMap[currItem.parentLocationGroup].children.push(currItem);
      } else {
        if (tempMap[currItem.id]) {
          currItem.children = tempMap[currItem.id].children;
        }
        tempMap[currItem.id] = currItem;
        if (tempMap[currItem.parentLocationGroup] == undefined) {
          tempMap[currItem.parentLocationGroup] = { children: [] };
        }
        tempMap[currItem.parentLocationGroup].children.push(currItem);
      }
      if (currItem.lgLevel == "0") {
        if (level0.indexOf(currItem) == -1) {
          level0.push(currItem);
        }
      }
    }
    this.levelData = level0;
  }

Zagregowane dane są przekazywane jako dane wejściowe do dropdownkomponentu, który czyni je wielopoziomowym menu rozwijanym.

To rozwiązanie powinno działać na każdym poziomie dzieci. dropdownKomponent może być modyfikowany, aby zmienić sposób, w jaki dane są renderowane jak na swoje wymagania.

Wziąłem htmlicss dla wielopoziomowego menu rozwijanego stąd:
https://phppot.com/css/multilevel-dropdown-menu-with-pure-css/
kod, aby zamknąć menu rozwijanym po kliknięciu na zewnątrz od tej odpowiedzi:
https: //stackoverflow.com/a/59234391/9262488

Mam nadzieję, że to było przydatne.

NiK648
źródło
Sprawdziłem twój kod. To działa. Dziękujemy za opublikowanie odpowiedzi. Czy możesz mi powiedzieć, w jaki sposób otrzymujesz moje dane. na żądanie http (mocky.io).
Arvind Chourasiya
Mocky to internetowe narzędzie do tworzenia interfejsu API próbnego odpoczynku. Wziąłem dane, które opublikowałeś i wykorzystałem je do stworzenia interfejsu API spoczynku przy użyciu kpiny.
NiK648
1

Dlaczego nie utworzyć komponentu drzewa i powiązać z nim rekurencyjnie dane wejściowe?

Proponowane rozwiązanie to

  • deep-agnostic - będzie działać na dowolnej liczbie poziomów w drzewie danych (nawet jeśli zmieni się ad-hoc)
  • dość wydajny - agreguje twoje dane O(n).

Najpierw zaprojektuj model danych - musi to być struktura węzła drzewa:

export interface GroupId { /* appropriate members... */ }

export interface ParentLocationGroup { /* other members... */ __text: string; }

export interface LgLevel { /* other members... */ __text: string; }

export interface DataModel {
  Id: string,
  Name: string,
  GroupId: GroupId,
  ParentLocationGroup: ParentLocationGroup,
  LgLevel: LgLevel,
  children: DataModel[]
}

Następnie agreguj swoje dane w komponencie najwyższego poziomu (lub jeszcze lepiej - w usłudze danych; powinieneś być w stanie wystarczająco łatwo streścić):

// dropdown.component.ts

@Component({
  selector: 'app-dropdown',
  template: `
    <ul class="nav site-nav">
      <li class=flyout>
        <a href=#>Dropdown</a>
        <app-dynamic-flyout [data]="data"></app-dynamic-flyout>
      </li>
    </ul>
  `
})
export class DropdownComponent {

  data: DataModel[];

  constructor(dataService: YourDataService){

    let data;
    dataService.getYourData()
      .pipe(map(d => d.ArrayOfLocationGroup.LocationGroup)))
      // Make sure every item has the `children` array property initialized
      // since it does not come with your data
      .subscribe(d => data = d.map(i => ({...i, children: []})));

    // Create a lookup map for building the data tree
    let idMap = data.reduce((acc, curr) => ({...acc, [curr.Id]: curr}), {});
    this.data = data.reduce(
      (acc, curr) => curr.LgLevel.__text == 0 || !curr.ParentLocationGroup
        // Either the level is 0 or there is no parent group
        // (the logic is unclear here - even some of your lvl 0 nodes have a `ParentGroup`)
        // -> we assume this is a top-level node and put it to the accumulator
        ? [...acc, curr]
        // Otherwise push this node to an appropriate `children` array
        // and return the current accumulator
        : idMap[curr.ParentLocationGroup.__text].children.push(curr) && acc, 
      []
    );
  }
}

I utwórz powtarzalny dynamiczny komponent wysuwany:

// dynamic-flyout.component.ts

@Component({
  selector: 'app-dynamic-flyout',
  template: `
    <ul *ngIf="!!data.length" class="flyout-content nav stacked">
      <li *ngFor="let item of data" [class.flyout-alt]="!!item.children.length">
        <a href=#>{{item.Name}}</a>
        <app-dynamic-flyout [data]="item.children"></app-dynamic-flyout>
      </li>
    </ul>
  `
})
export class DynamicFlyoutComponent {
  @Input() data: DataModel[];
}

Rozwiązanie nie zostało przetestowane, ale skieruje cię w dobrym kierunku ...

Mam nadzieję, że to trochę pomoże :-)

Heehaaw
źródło