DevExtreme Pivot Grid & Angular: prikaz i analiza višedimenzionalnih podataka

U ovom ću blog postu pokazati kako implementirati DevExtreme Pivot Grid UI komponentu u Angular projekt. Ona omogućava prikaz i analizu višedimenzionalnih podataka iz lokalne pohrane ili OLAP kocke.

Fokusirat ću se na prikaz podataka prema specifičnim parameterima te načinu na koji te podatke uređivati jer Pivot Grid nativno ne nudi takvu mogućnost.

Pivot Grid prikaz podataka

Prva i osnovna značajka Pivot Grida je prikaz i analizu višedimenzionalnih podataka.

U ovom ću primjeru koristiti strukturu podataka prikazanu u JSON-u kojega je moguće vidjeti ispod. S jedne strane biti će poslovnice, a s druge automobili. Cilj je prikazati količinu naručenih tj. isporučenih automobila ovisno o poslovnici.

Logika aplikacije nalazit će se unutar angular-devextreme.component.ts datoteke koja se temelji na PivotGridDataSource objektu. Za sada će se unutar njega nalaziti samo fields niz i store objekt.

Poslovnice će se nalaziti krajnje lijevo area: “row”, a automobili na vrhu area: “column”. Sve ostalo tj. narudžbe prikazuju se kao podaci area: “data”.

import { Component, OnInit } from '@angular/core';

import PivotGridDataSource from "devextreme/ui/pivot_grid/data_source";

@Component({
  selector: 'app-narudzbe-popis',
  templateUrl: './narudzbe-popis.component.html',
  styleUrls: ['./narudzbe-popis.component.scss']
})

export class NarudzbePopisComponent implements OnInit {

  dataSource: PivotGridDataSource;

  podaciDemo = [
    {
      "poslovnica_ID": 163,
      "poslovnica_naziv": "POSLOVNICA VK",
      "narudzba_ID": 1720303,
      "narudzba_narucenakolicina": 50,
      //Od 50 isporučeno 49 automobila
      "narudzba_isporucenakolicina": 49,
      "artikl_ar_ID": 69567,
      "artikl_sifra": 666627,
      "artikl_naziv": "Tesla Model S"
    },
    {
      "poslovnica_ID": 163,
      "poslovnica_naziv": "POSLOVNICA VK",
      "narudzba_ID": 83572434,
      "narudzba_narucenakolicina": 20,
      //Još nije unesena isporučena količina
      "narudzba_isporucenakolicina": null,
      "artikl_ar_ID": 69463,
      "artikl_sifra": 555018,
      "artikl_naziv": "Fiat Panda"
    },
    {
      "poslovnica_ID": 163,
      "poslovnica_naziv": "POSLOVNICA VK",
      "narudzba_ID": 83572437,
      "narudzba_narucenakolicina": 6,
      //Od 6 isporučeno 0 automobila
      "narudzba_isporucenakolicina": 0,
      "artikl_ar_ID": 69610,
      "artikl_sifra": 102836,
      "artikl_naziv": "Kia Rio"
    },
...
]

  constructor() {}

  ngOnInit(): void {
      this.prikaziNarudzbe();
  }

  prikaziNarudzbe(){
          this.dataSource = new PivotGridDataSource({
            fields: [
              {
                dataField: "poslovnica_naziv",
                dataType: "string",
                area: "row",
                width: 120
              },
              {
                dataField: "artikl_naziv",
                dataType: "string",
                area: "column",
                expanded: true
              },
              {
                caption: "Naručeno",
                dataField: "narudzba_narucenakolicina",
                dataType: "number",
                summaryType: "sum",
                area: "data"
              },
              {
                caption: "Isporučeno",
                dataField: "narudzba_isporucenakolicina",
                dataType: "number",
                summaryType: "sum",
                area: "data"
              }
            ],
            store: {
              type: "array",
              data: this.podaciDemo
            }
        });
  }

}

Izgled sučelja biti će definiran unutar angular-devextreme.component.html datoteke.

Od Pivot Grid UI svojstva koristit ću ih nekoliko:

  • showBorders – želim da vanjski rubovi budu vidljivi.
  • showColumnGrandTotals – ne želim prikazivati ukupne vrijednosti za kolone. Ovo bi se inače prikazivalo krajnje desno.
  • showRowGrandTotals – želim na dnu prikazati ukupne vrijednosti.
  • <dx-pivot-grid
                [showBorders]="true"
                [showColumnGrandTotals]="false"
                [showRowGrandTotals]="true"
                [dataSource]="dataSource">
                <dxo-field-chooser [enabled]="false" [height]="400"></dxo-field-chooser>
                <dxo-scrolling
                    mode="standard"  
                    [useNative]="false"
                    [scrollByContent]="true"
                    [scrollByThumb]="true"
                    showScrollbar="always">
                </dxo-scrolling> <!-- "virtual" | "infinite" -->
    </dx-pivot-grid>

    Sve to zajedno na kraju daje sljedeće:

    DevExtreme Pivot Grid - osnovni prikaz

    Pivot Grid – promjena izgleda polja

    Ako želim dodatno prilagoditi prikaz određenih polja to mogu napraviti koristeći onCellPrepared metodu.

    <dx-pivot-grid ...
        (onCellPrepared)="onCellPrepared($event)">
    </dx-pivot-grid>

    U ovom slučaju hoću napraviti tri grafičke izmjene. Stupac “Naručeno” će imati sivu pozadinsku boju i podebljane brojeve, ukupne vrijednosti na dnu također će biti podebljanje, a vrijednosti koje za “Isporučeno” imaju null će imati crvenu pozadinsku boju kako bi bila uočljivija.

    onCellPrepared(e){
          
        if(e.cell.rowType == "GT"){
          e.cellElement.style.fontWeight = "bold";
        }
    
        if(e.area == 'data' && e.columnIndex % 2 == 0){
              e.cellElement.style.fontWeight = "bold";
              e.cellElement.style.backgroundColor = "rgb(242,242,242)";
        }
        
        if(e.area === "data" && e.cell.dataIndex === 1 && e.cell.value === null && e.cell.rowType != "GT"){
            e.cellElement.style.backgroundColor = "rgb(255 0 0 / 42%)";
        }
              
      }

    Na ekranu bi to izgledalo ovako:

    Pivot Grid onCellPrepared

    Međutim, niti jedno polje nema crvenu pozadinsku boju. Razlog je što Pivot Grid sve null vrijednosti vidi kao 0 i zato uvjet e.cell.value === null nije zadovoljen. Ali ako stavim e.cell.value === 0 stanje će biti puno drugačije.

    Pivot Grid onCellPrepared

    Iz podaciDemo niza se može vidjeti da narudzba_isporucenakolicina može imati vrijednosti null. To znači da bi vrijednost za “POSLOVNICA VK” i automobil “Fiat Panda” trebala imati crvenu pozadinsku boju jer je narudzba_isporucenakolicina = null, a kombinacija POSLOVNICA VK” i “Kia Rio” ne bi trebala imati crvenu pozadinsku boju jer ima vrijednost narudzba_isporucenakolicina = 0.

    Pivot Grid – prikaz null vrijednosti

    Kako bi u PivotGridu mogao prikazati null vrijednosti i razlikovati ih od vrijednosti 0 koristiti ću calculateCustomSummary funkciju. Tu ću funkciju vezati uz polje dataField: “narudzba_isporucenakolicina”.

    {
            caption: "Isporučeno",
            dataField: "narudzba_isporucenakolicina",
            dataType: "number",
            //summaryType: "sum",
            area: "data",
            summaryType: "custom",
            calculateCustomSummary: function(options) {  
              if (options.summaryProcess == 'start') {  
                  options.totalValue = null;  
              }  
              if (options.summaryProcess == 'calculate') {  
                  if (options.value !== null) {  
                      options.totalValue += options.value;  
                  }  
              }  
            }
    }

    Sada se na ekranu može vidjeti da istovremeno imam prikazane vrijednosti za isporučenu količinu i kao null i kao 0.

    Pivot Grid calculateCustomSummary

    Omogućiti klik na odabrano polje

    Želim omogućiti klik samo na polje koje pokazuje isporučenu količinu, ali isključivo za poslovnice i automobile koji imaju unesenu naručenu količinu. Znači, ne želim da se u koloni sa isporučenom količinom može kliknuti na prazno polje jer nema smisla unositi isporučenu količinu ako ništa nije naručeno.

    Za to ću koristiti onCellClick funkciju.

    <dx-pivot-grid ...
        (onCellClick)="onPivotCellClick($event)">
    </dx-pivot-grid>
    onPivotCellClick(e) {
        //Klik na polje "narudzba_isporucenakolicina" ako ima unesenu vrijednost
        if(e.area == "data" && e.cell.dataIndex == 1 && e.cell.value != undefined && e.cell.rowType != "GT") {
          console.log(e);
        }
        //Klik na polje "narudzba_isporucenakolicina" ako je vrijednosti null tj. ako je polje crvene pozadinske boje
        if(e.area === "data" && e.cell.dataIndex === 1 && e.cell.value === null && e.cell.rowType != "GT"){
          console.log(e);
        }
     }

    Iz prikazanog se može vidjeti da klik na prazno polje ne radi ništa, klik na crveno polje dohvaća vrijednost null, a klik na polje za unesenom količinom dohvaća tu vrijednost. U ovom slučaju ta vrijednost je 10.

    Pivot Grid onCellClick

    Pivot Grid dohvaćanje vrijednosti polja

    Pivot Grid je primarno kreiran s ciljem prikaza podataka tj. prema dokumentaciji nema mogućnost jednostavnog uređivanja podataka kao što je to slučaj kod Data Grida, barem ju nisam našao, ali sam znao da mora postojati neko rješenje.

    Za početak želim prikazati nekakvu formu unutar koje će se vrijednost polja moći urediti. Za to ću iskoristiti Popup UI komponentu. Unutar nje definiram dxTemplate što mi omogućava daljnju prilagodbu sučelja za uređivanje isporučene vrijednosti.

    Za dohvaćanje vrijednosti kliknutog polja koristit ću Drill Down.

    ...
    </dx-pivot-grid>
            <dx-popup
            [closeOnOutsideClick]="false"
            [showCloseButton]="true"
            [(visible)]="popupVisible"
            [title]="popupTitle"
            [width]="650"
            [height]="200"
            >
            <div *dxTemplate="let data of 'content'">
                <dx-data-grid
                #drillDownDataGrid
                [dataSource]="drillDownDataSource"
                >
                </dx-data-grid>
            </div>
            </dx-popup>
    import { 
            Component,
            OnInit,
            ViewChild
          } 
          from '@angular/core';
    
    import { DxTextBoxComponent } from 'devextreme-angular';
       ...
    
    export class NarudzbePopisComponent implements OnInit {
    
      popupVisible = false;
      popupTitle: string;
      narudzba_ID: number;
      isporucenakolicina: number;
      narucenaKolicinaPopup: number;
    
      drillDownDataSource: any;
    
      @ViewChild("kolicinaUnos", { static: false }) inputName: DxTextBoxComponent;
    ...
    
    onPivotCellClick(e) {
        //Klik na polje "narudzba_isporucenakolicina" ako ima unesenu vrijednost
        if(e.area == "data" && e.cell.dataIndex == 1 && e.cell.value != undefined && e.cell.rowType != "GT") {
          //console.log(e);
          this.drillDownDataSource = this.dataSource.createDrillDownDataSource(e.cell);
            let _this = this;
            this.drillDownDataSource.load().done(function(result) {
              _this.narucenaKolicinaPopup = result[0].nas_kolicina;
            });
            this.popupTitle = e.cell.rowPath[0] + ' - ' + e.cell.columnPath[0];
            this.popupVisible = true;
        }
        //Klik na polje "narudzba_isporucenakolicina" ako je vrijednosti null tj. ako je polje crvene pozadinske boje
        if(e.area === "data" && e.cell.dataIndex === 1 && e.cell.value === null && e.cell.rowType != "GT"){
          //console.log(e);
          this.drillDownDataSource = this.dataSource.createDrillDownDataSource(e.cell);
            let _this = this;
            this.drillDownDataSource.load().done(function(result) {
              _this.narucenaKolicinaPopup = result[0].nas_kolicina;
            });
            this.popupTitle = e.cell.rowPath[0] + ' - ' + e.cell.columnPath[0];
            this.popupVisible = true;
        }
      }

    Klikom na polje isporučene količine prikazat će se sljedeće:

    PivotGrid DxPopup DrillDown

    Kao što se iz prikazanog može vidjeti dohvaćeni su podaci prikazani na vrhu blog posta.

    {
          "poslovnica_ID": 163,
          "poslovnica_naziv": "POSLOVNICA VK",
          "narudzba_ID": 1720303,
          "narudzba_narucenakolicina": 50,
          //Od 50 isporučeno 49 automobila
          "narudzba_isporucenakolicina": 49,
          "artikl_ar_ID": 69567,
          "artikl_sifra": 666627,
          "artikl_naziv": "Tesla Model S"
    }

    Pivot Grid uređivanje vrijednosti polja

    Popup iz gornjeg primjera je potrebno prilagoditi na način da se kreira input polje za izmjenu isporučene količine. Klikom na polje sa već unesenom isporučenom količinom ista će se prikazati unutar input polja. Ako je polje prazno isto će biti i input polje.

    U GIF-u se može primjetiti da uređivanje isporučene količine uredno prolazi, ali uz jedan problem. Prilikom svakog spremanja isporučene količine cijeli se Pivot Grid osvježi i vrati u krajnju lijevu poziciju. Naravno da to nije praktično. Uz to, prilikom otvaranja popupa polje za unos količine nije fokusirano (crvena podcrtana linija) nego je na njega prvo potrebno kliknuti.

    U nastavku ću pokazati kako riješiti oba problema.

    Pivot Grid uređivanje

    <div *dxTemplate="let data of 'content'">
                <dx-data-grid
                class="dataGridKlasa"
                #drillDownDataGrid
                [dataSource]="drillDownDataSource"
                >
                    <dxi-column dataField="narudzba_isporucenakolicina" caption="Isporučena količina" [visible]="true" cellTemplate="urediKolicinu">
                        <div *dxTemplate="let data of 'urediKolicinu'">
                            <dx-number-box #kolicinaUnos
                                style="text-align: center;"
                                [value]="data.value"
                                [placeholder]="data.value"
                                (onValueChanged)="spremiKolicinu($event, data)"
                                (onEnterKey)="spremiKolicinuEnter($event, data)"
                                >
                            </dx-number-box>
                        </div>
                    </dxi-column>
                    <dxi-column cellTemplate="gumbUrediKolicinu">
                        <div *dxTemplate="let data of 'gumbUrediKolicinu'">
                            <dx-button
                                style="margin-bottom: 5px;"
                                stylingMode="contained"
                                text="SPREMI"
                                type="success"
                                [width]="120"
                                (onClick)="spremiKolicinuButton($event, data)">
                            </dx-button>
                        </div>
                    </dxi-column>
                </dx-data-grid>
            </div>
      spremiKolicinu(event, data){
        this.isporucenakolicina = event.value;
        this.narudzba_ID = data.row.cells[0].data.narudzba_ID;
      }
    
      spremiKolicinuButton(event, data){
        //Slanje podataka na API
        //if(res.success == true){
          this.popupVisible = false;
        //} else {
          //Poruka o grešci
        //}
      }
    
      spremiKolicinuEnter(event, data){
        // this.slanjeKolicine(data.key.nas_ID, e.value);
          //Slanje podataka na API
          //if(res.success == true){
            this.popupVisible = false;
          //} else {
            //Poruka o grešci
          //}
      }

    Osvježavanje samo uređenog podatka

    Jedan od problema koji se stvorio nakon što je omogućeno uređivanje podataka je to što se sada cijeli ekran osvježi i onda se svaki put iznova mora skrolati do točno određenog polja kako bi se nastavilo uređivanje. To nikako nije praktično i bilo je potrebno pronaći rješenje.

    U ovom će mi slučaju pomoći ArrayStore, ali tek kada prilagodim prikaziNarudzbe() funkciju. Kao što se može vidjeti this.podaciDemo se sada dohvaćaju upravo kroz ArrayStore. Kroz key parametar identificiram unikatnu vrijednost pomoću koje Pivot Grid može znati koju vrijednost će osvježiti.

    Podatke nakon unosa osvježavam pomoću update(key, values) metode unutar funkcija spremiKolicinuButton() i spremiKolicinuEnter().

    import { Component, OnInit, ViewChild } from '@angular/core';
    
    import PivotGridDataSource from "devextreme/ui/pivot_grid/data_source";
    import ArrayStore from "devextreme/data/array_store";
    import { DxTextBoxComponent,
             DxPivotGridComponent } from 'devextreme-angular';
    ...
    
    @ViewChild(DxPivotGridComponent, { static: false }) pivotGrid: DxPivotGridComponent;
    
    dataSource: PivotGridDataSource;
    dataStore: any;
    
    ...
    
    prikaziNarudzbe(){
              this.dataStore = new ArrayStore({
                key: "narudzba_ID",
                data: this.podaciDemo
              });
              this.dataSource = new PivotGridDataSource({
                fields: [
                  {
                    dataField: "poslovnica_naziv",
                    dataType: "string",
                    area: "row",
                    width: 120
                  },
                  {
                    dataField: "artikl_naziv",
                    dataType: "string",
                    area: "column"
                  },
                  {
                    caption: "Naručeno",
                    dataField: "narudzba_narucenakolicina",
                    dataType: "number",
                    summaryType: "sum",
                    area: "data"
                  },
                  {
                    caption: "Isporučeno",
                    dataField: "narudzba_isporucenakolicina",
                    dataType: "number",
                    area: "data",
                    summaryType: "custom",
                    calculateCustomSummary: function(options) {  
                      if (options.summaryProcess == 'start') {  
                          options.totalValue = null;  
                      }  
                      if (options.summaryProcess == 'calculate') {  
                          if (options.value !== null) {  
                              options.totalValue += options.value;  
                          }  
                      }  
                    }
                  }
                ],
                store: this.dataStore
            });
      }
    
    ...
    
    spremiKolicinuButton(event, data){
        //Slanje podataka na API
        //if(res.success == true){
          this.dataStore.update(data.key, {
            'poslovnica_ID': data.data.poslovnica_ID,
            'poslovnica_naziv': data.data.poslovnica_naziv,
            'artikl_ar_ID': data.data.artikl_ar_ID,
            'artikl_naziv': data.data.artikl_naziv,
            'artikl_sifra': data.data.artikl_sifra,
            'narudzba_isporucenakolicina': this.isporucenakolicina,
            'narudzba_narucenakolicina': data.data.narudzba_narucenakolicina,
            'narudzba_ID': data.data.narudzba_ID
          });
          this.isporucenakolicina = null;
          this.pivotGrid.instance.getDataSource().reload();
          this.popupVisible = false;
        //} else {
          //Poruka o grešci
        //}
      }
    
      spremiKolicinuEnter(event, data){
        // this.slanjeKolicine(data.key.nas_ID, e.value);
          //Slanje podataka na API
          //if(res.success == true){
            this.dataStore.update(data.key, {
              'poslovnica_ID': data.data.poslovnica_ID,
              'poslovnica_naziv': data.data.poslovnica_naziv,
              'artikl_ar_ID': data.data.artikl_ar_ID,
              'artikl_naziv': data.data.artikl_naziv,
              'artikl_sifra': data.data.artikl_sifra,
              'narudzba_isporucenakolicina': this.isporucenakolicina,
              'narudzba_narucenakolicina': data.data.narudzba_narucenakolicina,
              'narudzba_ID': data.data.narudzba_ID
            });
            this.isporucenakolicina = null;
            this.pivotGrid.instance.getDataSource().reload();
            this.popupVisible = false;
          //} else {
            //Poruka o grešci
          //}
      }

    Uz to, kada se popup otvori polje za unos isporučene količine nije fokusirano i prije unosa potrebno je na njega kliknuti. To također nije praktično. Ovo se vrlo lako riješi kroz onShown funkciju.

    <dx-popup
            [closeOnOutsideClick]="false"
            [showCloseButton]="true"
            [(visible)]="popupVisible"
            [title]="popupTitle"
            [width]="650"
            [height]="200"
            (onShown)="onPopupShown($event)"
            >
    onPopupShown(e) {
        let a = this;
        setTimeout(() => {
          a.inputName.instance.focus();
        }, 100);
    }

    Na kraju to izgleda ovako:

    PivotGrid Reload

    Zaključak

    Naravno, ovo je samo dio mogućnosti koje DevExtreme Pivot Grid ima, ali dovoljno da pohvatate osnove i shvatite na koji način funkcionira.

    Prikaz podataka API-ja Sudskog registra u Angular formi

    U ovom ću blog postu pokazati kako napraviti upit na API Sudskog registra i dohvatiti informacije o poslovnom subjektu prema OIB-u/MBS-a. U dokumentaciji se može vidjeti koje je sve informacije i preko kojih ruta moguće napraviti, ali ja ću se zadržati na onoj po meni najzanimljivijoj subjekt_detalji_Get.

    Projekt je dostupan na GitHubu.

    Uvod

    Za kreiranje forme koristiti ću FormBuilder, a mogao sam isto tako koristiti i ngModel ili FormControl. Više o tome objavio sam u jednom od prijašnjih blog postova. Forma će se sastojati od nekoliko polja od kojih će većina biti obavezna. Prilikom unosa OIB-a ili MBS-a odmah ide provjera na API Sudskog registra i tek nakon te provjere moguće je kliknuti na gumb “Spremi” nakon kojega se podaci mogu spremiti u bazu podataka.

    Provjera rada API-ja

    Prije nego krenem s izradom Angular forme idem se uvjeriti da API radi na način kako je prikazano u dokumentaciji i da mi vraća podatke koje želim koristiti. API je moguće testirati i na ovoj adresi. Da nema te forme koristio bi Postman.

    URL na koji radim upit je:

    https://sudreg-api.pravosudje.hr/javni/subjekt_detalji?tipIdentifikatora={tipIdentifikatora}&identifikator={identifikator}[&expand_relations][&timestamp_id]

    Obavezni parametri upita su sljedeći:

    • tipIdentifikatora* (string) – moguće koristiti oib (osobni identifikacijski broj) ili mbs (matični broj subjekta)
    • identifikator* (integer) – oib ili mbs koristim u obliku broja (int64)
    • Ocp-Apim-Subscription-Key* (string) – ide u header, a dobije se nakon registracije

    Kreiranje API servisa

    U servisu će se nalaziti dvije funkcije. Jedna će dohvaćati podatke prema OIB-u, a druga prema MBS-a.

    import { Injectable } from '@angular/core';
    import { HttpClient, HttpHeaders } from '@angular/common/http';
    import { map } from 'rxjs/operators';
    
    @Injectable({
      providedIn: 'root'
    })
    export class ApiService {
    
       httpOptions = {
        headers: new HttpHeaders({
          'Ocp-Apim-Subscription-Key': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
        })
      };
    
      constructor(private _http: HttpClient) { }
    
      sudskiRegistarOIB(oib:any) {
        return this._http.get('https://sudreg-api.pravosudje.hr/javni/subjekt_detalji?tipIdentifikatora=oib&identifikator='+oib+'&expand_relations=true', this.httpOptions)
          .pipe(map((res: any) => res ));
      }
    
      sudskiRegistarMBS(mbs:any) {
        return this._http.get('https://sudreg-api.pravosudje.hr/javni/subjekt_detalji?tipIdentifikatora=mbs&identifikator='+mbs+'&expand_relations=true', this.httpOptions)
          .pipe(map((res: any) => res ));
      }
    
    }

    HTML forma

    Kreiram formu sa poljima:
    oib – dužina 11 znakova. Obavezno polje.
    mbs – dužina 9 znakova. Obavezno polje.
    nazivtvrtke – Obavezno polje.
    ulica – Obavezno polje.
    grad – Obavezno polje.
    e-mail

    Prilikom unosa OIB-a ili MBS-a odmah se vrši provjera na API putem funkcije provjera(angularForma). Tek nakon što se forma ispuni podacima tj. kada se zadovolji unos obaveznih polja moguće je kliknuti na gumb “Spremi”.

     <form class="form-content" id="angularForma" [formGroup]="angularForma" (ngSubmit)="spremi()">
           <div class="form-header">
                     <h5 class="title">Unos poslovnog subjekta</h5>
                </div>
                <div class="form-body">
                    <div class="form-group row">
                        <div class="col-sm-6">
                            <input class="form-control form-control-line" type="text" pattern="[0-9]{11}" minlength="11" maxlength="11" formControlName="oib" placeholder="OIB" (keyup)="provjera(angularForma)">
                        </div>
                        <div class="col-sm-6">
                            <input class="form-control form-control-line" type="text" pattern="[-+]?[0-9]*[.,]?[0-9]+" minlength="9" maxlength="9" formControlName="mbs" placeholder="MBS" (keyup)="provjera(angularForma)">
                        </div>
                    </div>
                        <div class="form-group">
                            <input class="form-control form-control-line" type="text" formControlName="nazivtvrtke" placeholder="Naziv">
                        </div>
                        <div class="form-group">
                            <input class="form-control form-control-line" type="text" formControlName="ulica" placeholder="Adresa">
                        </div>
                        <div class="form-group">
                            <input class="form-control form-control-line" type="text" formControlName="grad" placeholder="Grad">
                        </div>
                        <div class="form-group">
                            <input class="form-control form-control-line" type="email" formControlName="email" pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$" placeholder="E-mail">
                        </div>
                </div>
                <div class="form-footer justify-content-between">
                    <div class="text-center">
                        <button class="btn btn-primary btn-rounded" [disabled]="!angularForma.valid" type="submit">Spremi</button>
                    </div>
           </div>
    </form>

    Funkcionalnost forme

    Unutar ngOnInit() funkcije radim inicijalizaciju forme. Ovdje odmah dodajem i Validators kako bi mogao validirati potrebna polja i spriječiti klik na gumb “Spremi” pomoću [disabled]=”!angularForma.valid” u slučaju kada forma nije ispravno ispunjena.

    Kada se unutar polja forme unesu OIB ili MBS poziva se funkcija provjera(angularForma). Prvo vršim provjeru koja vrsta identifikatora (OIB ili MBS) je unesena jer ću kasnije na osnovu toga pozivati pripadajući API Sudskog registra iz ApiService i paralelno provjeravam da su ispravne dužine. Nakon toga provjeravam da unutar unesenih znakova nema slova tj. da su samo brojevi match(/^[0-9]+$/). Kada su svi navedeni uvjeti zadovoljeni pozivam this._apiService.sudskiRegistarOIB ili this._apiService.sudskiRegistarMBS te prikazujem dobivene podatke console.log(res) i odmah popunjavam formu.

    Kada je forma ispravno validirana podaci će se uspješno dohvatiti.

    Angular forma: Sudski Registar

    Ne međutim, ostaje problem kod popunjavanja forme sa nasumičnim brojevima koji će ispravno biti validirani prema dužini iako ne postoje u Registru. Npr. netko za OIB može unijeti 11 nula ili za MBS 9 nula i forma će biti validna te će se taj poslovni subjekt moći spremiti u bazu podatka.

    Angular forma

    To mogu spriječiti koristeći setValue ili patchValue. Znači, ako mi API Sudskog registra ne vrati podatke neću dopustiti da forma prođe kao validna i da se uopće može kliknuti na “Spremi”.

    this.angularForma.controls[‘oib’].setValue(null);
    this.angularForma.controls[‘oib’].patchValue(null);

    Angular - Sudski Registar

    Što se adrese tiče stavio sam uvjet da se prikaže ispravna vrijednost neovisno o tome ima li prikazana adresa uz broj i podbroj tj. slovo (res.sjedista[0].kucni_podbroj ? (res.sjedista[0].kucni_podbroj) : ”) .

    Npr. u adresi Dragutina Žanića Karle 27a je ‘a’ podbroj .

    import { Component, OnInit } from '@angular/core';
    import { FormGroup, FormBuilder, Validators } from '@angular/forms';
    
    import { ApiService } from './api.service';
    
    import * as toastr from 'toastr';
    
    @Component({
      selector: 'app-angular-forma',
      templateUrl: './angular-forma.component.html',
      styleUrls: ['./angular-forma.component.css'],
      providers: [ApiService]
    })
    export class AngularFormaComponent implements OnInit {
    
      angularForma: FormGroup;
    
      constructor(private _fb: FormBuilder,
                  private _apiService: ApiService) { }
    
      ngOnInit() {
        //Inicijalizacija forme
        this.angularForma = this._fb.group({
          oib: ['', [Validators.required, Validators.minLength(11), Validators.maxLength(11), Validators.pattern('[-+]?[0-9]*[.,]?[0-9]+')]],
          mbs: ['', [Validators.required, Validators.minLength(9), Validators.maxLength(9), Validators.pattern('[-+]?[0-9]*[.,]?[0-9]+')]],
          nazivtvrtke: ['', Validators.required],
          ulica: ['', Validators.required],
          grad: ['', Validators.required],
          email: ['', Validators.email]
        }); 
      }
    
     provjera(angularForma){
          //OIB
          if(angularForma.value.oib && angularForma.value.oib.length == 11){
            if(angularForma.value.oib.match(/^[0-9]+$/)){
              this._apiService.sudskiRegistarOIB(angularForma.value.oib).subscribe(res => {
                console.log(res);
                if(res){
                    this.angularForma = this._fb.group({
                      oib: res.oib,
                      mbs: res.mbs,
                      nazivtvrtke: res.skracene_tvrtke[0].ime,
                      ulica: res.sjedista[0].ulica + ' ' + res.sjedista[0].kucni_broj + (res.sjedista[0].kucni_podbroj ? (res.sjedista[0].kucni_podbroj) : ''),
                      grad: res.sjedista[0].naziv_naselja,
                      email: ''
                    });
                } else {
                  toastr.error("Neuspješno dohvaćanje podataka prema OIB!");
                  this.angularForma.controls['oib'].setValue(null);
                }
              });
            } else {
              toastr.error("OIB mora biti u obliku broja!");
            }
          //MBS
          } else if(angularForma.value.mbs && angularForma.value.mbs.length == 9){
            if(angularForma.value.mbs.match(/^[0-9]+$/)){
              this._apiService.sudskiRegistarMBS(angularForma.value.mbs).subscribe(res => {
                console.log(res);
                if(res){
                    this.angularForma = this._fb.group({
                      oib: res.oib,
                      mbs: res.mbs,
                      nazivtvrtke: res.skracene_tvrtke[0].ime,
                      ulica: res.sjedista[0].ulica + ' ' + res.sjedista[0].kucni_broj + (res.sjedista[0].kucni_podbroj ? (res.sjedista[0].kucni_podbroj) : ''),
                      grad: res.sjedista[0].naziv_naselja,
                      email: ''
                    });
                } else {
                  toastr.error("Neuspješno dohvaćanje podataka prema MBS!");
                  this.angularForma.controls['mbs'].setValue(null);
                }
              });
            } else {
              toastr.error("MBS mora biti u obliku broja!");
            }
          } else {
            //
          } 
      }
    
      spremi(){
        console.log(this.angularForma);
      }
    
    }

    *ngFor u Angularu 101

    Cilj blog posta “*ngFor u Angularu za početnike” je napraviti uvod u ngFor Angular direktivu jer ona je osnova koju svaki, Angular, developer treba i mora znati.

    *ngFor direktiva se primarno koristi za prikaz niza podataka (array) i početnicima može predstavljati poseban izazov. Također, može se koristiti i za prikaz objekata uz pomoć KeyValuePipe.

    Prije nego krenem s izradom primjera trebam kreirati Angular projekt, a to činim naredbom:

    $ ng new AngularNgFor
    $ cd AngularNgFor
    $ ng serve

    Projekt je vidljiv na adresi http://localhost:4200/

    Uvod

    *ngFor direktiva u Angularu, osim što služi za prikaz niza podataka, je optimizirana da minimalno utječe na DOM (Document Object Model).

    To će se najbolje vidjeti u nastavku kada budem koristio let i = index pri čemu ću uklanjati elemente niza bez da ga cijelog ponovno osvježavam.

    Angular će se pobrinuti za to da samo taj jedan element izbaci dok sve drugo ostaje isto. Isti slučaj bi bio i kada bi htio dodati novi element u niz.

    U slučaju kada podatke dohvaćamo putem nekog API-ja dogodit će se, u slučaju kada se neki podataka obriše ili doda, da Angular više neće znati što treba biti na kojoj poziciji. U tom slučaju koristim trackBy putem čega Angularu mogu reći koji parametar da koristi kako bi identificirao elemente.

    *ngFor – prikaz niza podataka

    Ako npr. imam sljedeći niz podatka za pretpostaviti je da ih želim na ekranu prikazati kao popis. U ovom će slučaju to biti jednostavan popis osoba. Podaci su fiksno određeni unutar niza, ali logika je ista čak i da stižu putem nekog API-ja.

    simpleArray = [
        {
          id: 4,
          name: 'Tomislav',
          country: 'Croatia'
        },
        {
          id: 5,
          name: 'Jomislav',
          country: 'Croatia'
        },
        {
          id: 6,
          name: 'Valsimot',
          country: 'Croatia'
        },
      ]

    Gore navedeni popis podataka želim prikazati na sljedeći način:

    <ul>
      <li>Tomislav, Croatia</li>
      <li>Jomislav, Croatia</li>
      <li>Valsimot, Croatia</li>
    </ul>

    To znači da nekako moram postići da se za svaki objekt unutar niza kreira novi <li></li> element.

    Upravo tu uskače *ngFor sa vrlo jednostavnom sintaksom *ngFor=”let <value> of <collection>” pri čemu <value> označava naziv varijable koji proizvoljno određujem, dok <collection> označava izvor podataka što je u ovom slučaju niz simpleArray.

    <ul>
      <li *ngFor="let data of simpleArray">{{data.name}}, {{data.country}}</li>
    </ul>

    Konačni rezultat je sljedeći:

    **ngFor u Angularu za početnike

    Ako želim imati redne brojeve mogu koristiti ol tag. Ovo spominjem samo iz razloga što ću u nastavku pokazati kako je redne brojeve moguće dobiti i pomoću indexa elementa.

    *ngFor u Angularu za početnike

    *ngFor + element index

    index se definira kao varijabla let i = index i uvijek počinje od 0. Mogu ga koristiti kako bi prikazao redne brojeve elemenata.

    <ul>
      <li *ngFor="let data of simpleArray; let i = index">{{i+1}} - {{data.name}}, {{data.country}}</li>
    </ul>

    Na ekranu to izgleda ovako:

    *ngFor u Angularu za početnike

    index mogu, ali i ne moram prikazati na ekranu. U oba ga slučaja mogu koristiti kako bi pristupio pojednom elementu niza.

    U sljedećem ću primjeru pomoću funkcije removeByIndex(i) ukloniti neke elemente iz niza.

    removeByIndex(i){
       console.log(i);
       this.simpleArray.splice(i,1);
       console.log(this.simpleArray);
    }

    Funkciju pozivam klikom na pojedini element.

    <ul>
      <li *ngFor="let data of simpleArray; let i = index" (click)="removeByIndex(i)">{{i}} - {{data.name}}, {{data.country}}</li>
    </ul>

    U praksi to izgleda ovako:

    Angular index element

    *ngFor + trackBy

    trackBy se definira kao ; trackBy: id.

    <ul>
      <li *ngFor="let data of simpleArray; let i = index; trackBy: id" (click)="trackElement(i,data.id)">{{data.name}}, {{data.country}}</li>
    </ul>

    Pomoću funkcije trackElement(i,data.id) dohvaćam unikatni ID svakog elementa niza.

    trackElement(i: number, element: any) {
        console.log("index:" + " " + i + ", " + "trackBy ID:" + " " + element);
    }

    U praksi to izgleda ovako:

    *ngFor trackBy

    *ngFor – prvi i zadnji element niza

    *ngFor direktiva u Angularu nudi još jednu zanimljivu mogućnost, a to je dohvaćanje prvog i zadnjeg elementa niza pomoću ; let first = first; let last = last.

    <ul>
      <li *ngFor="let data of simpleArray; let first = first; let last = last" [ngClass]="{ firstClass: first, lastClass: last }">{{data.name}}, {{data.country}}</li>
    </ul>

    Na taj se način prvi i zadnji element mogu npr. urediti CSS-om.

    .firstClass {
        color: red;
    }
    .lastClass {
        color: blue;
    }

    U praksi to izgleda ovako:

    *ngFor u Angularu za početnike

    Iako ovo još nisam koristio u nekoj produkcijskoj aplikaciji izgleda vrlo zanimljivo.

    *ngFor – parni i neparni elementi niza

    Kada već spominjem uređivanje elementata CSS-om, postoji još jedna zanimljiva funkcionalnost korištenjem ; let even = even; let odd = odd.

    <ul>
     <li *ngFor="let data of simpleArray; let even = even; let odd = odd" [ngClass]="{ evenClass: even, oddClass: odd }">{{data.name}}, {{data.country}}</li>
    </ul>

    Na taj se način parni i neparni elementi mogu npr. urediti CSS-om.

    .evenClass {
        color: red;
    }
    .oddClass {
        color: blue;
    }

    U praksi to izgleda ovako:

    *ngFor u Angularu za početnike

    *ngFor – niz unutar niza

    Osim prikaza osnovnog niza mogu prikazati i napredni niz tj. countryList niz unutar nestedArray niza.

    nestedArray: any[] = [
        {
          id: 4,
          name: 'Tomislav',
          countryList: [
            {
              shortCode: 'HR',
              fullName: 'Croatia'
            },
            {
              shortCode: 'UK',
              fullName: 'United Kingdom'
            }
          ]
        },
        {
          id: 5,
          name: 'Jomislav',
          countryList: [
            {
              shortCode: 'HR',
              fullName: 'Croatia'
            },
            {
              shortCode: 'UK',
              fullName: 'United Kingdom'
            }
          ]
        },
        {
          id: 6,
          name: 'Valsimot',
          countryList: [
            {
              shortCode: 'HR',
              fullName: 'Croatia'
            },
            {
              shortCode: 'UK',
              fullName: 'United Kingdom'
            }
          ]
        },
    ]

    U ovom slučaju *ngFor ide unutar <ul>.

    <ul *ngFor="let data of nestedArray">
       <li>{{data.name}}</li>
          <ul>
            <li *ngFor="let country of data.countryList">
                {{country.fullName}}
            </li>
          </ul>
    </ul>

    U praksi to izgleda ovako:

    *ngFor u Angularu za početnike

    Zaključak

    U ovom blog postu, pod nazivom “*ngFor u Angularu za početnike“, naveo sam osnovne funkcionalnosti *ngFor direktive s kojom svaki developer može napraviti funkcionalno rješenje. Sve dalje ovisi o specifičnim potrebama projekta.

    P.S. Struktura projekta prema package.json:

    {
      "name": "angular-ng-for",
      "version": "0.0.0",
      "scripts": {
        "ng": "ng",
        "start": "ng serve",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e"
      },
      "private": true,
      "dependencies": {
        "@angular/animations": "~7.2.0",
        "@angular/common": "~7.2.0",
        "@angular/compiler": "~7.2.0",
        "@angular/core": "~7.2.0",
        "@angular/forms": "~7.2.0",
        "@angular/platform-browser": "~7.2.0",
        "@angular/platform-browser-dynamic": "~7.2.0",
        "@angular/router": "~7.2.0",
        "core-js": "^2.5.4",
        "rxjs": "~6.3.3",
        "tslib": "^1.9.0",
        "zone.js": "~0.8.26"
      },
      "devDependencies": {
        "@angular-devkit/build-angular": "~0.13.0",
        "@angular/cli": "~7.3.9",
        "@angular/compiler-cli": "~7.2.0",
        "@angular/language-service": "~7.2.0",
        "@types/node": "~8.9.4",
        "@types/jasmine": "~2.8.8",
        "@types/jasminewd2": "~2.0.3",
        "codelyzer": "~4.5.0",
        "jasmine-core": "~2.99.1",
        "jasmine-spec-reporter": "~4.2.1",
        "karma": "~4.0.0",
        "karma-chrome-launcher": "~2.2.0",
        "karma-coverage-istanbul-reporter": "~2.0.1",
        "karma-jasmine": "~1.1.2",
        "karma-jasmine-html-reporter": "^0.2.2",
        "protractor": "~5.4.0",
        "ts-node": "~7.0.0",
        "tslint": "~5.11.0",
        "typescript": "~3.2.2"
      }
    }

    Kako pomoću SOAP protokola slati/primati XML u PHP-u

    Iako sam do sada radio isključivo s REST API-jem koristeći JSON u ovom ću blog postu pokazati kako pomoću SOAP protokola slati/primati XML u PHP-u.

    Nekoliko stvari će biti pojašnjeno:
    – kako treba izgledati XML kojeg šaljem kako bi dobio očekivani rezultat
    – kako raspakirati dobiveni rezultat i od njega napraviti niz (array) podataka
    – od čega se sastoji __doRequest metoda

    XAMPP

    S obzirom da ću sve raditi i testirati na lokalnom računalu potreban mi je XAMPP, jedno od popularnijih PHP razvojnih okruženja.

    Ako lokalno želim koristiti SOAP protokol moram aktivirati SoapClient jer ću inače dobiti sljedeću grešku:

    Fatal error: Uncaught Error: Class 'SoapClient' not found in C:\xampp\htdocs\soap\demo.php:57 Stack trace: #0 {main} thrown in C:\xampp\htdocs\soap\demo.php on line 57

    Aktivacija se vrši unutar Apache modula – ConfigPHP (php.ini).

    SOAP XAMPP

    Potrebno je pronaći ;extension=php_soap.dll i maknuti ; te nakon toga restartati Apache modul.

    SOAP XAMPP

    Postman

    Prije nego krenem s izradom primjera u PHP-u moram se uvjeriti da API radi, a to ću napraviti kroz Postman.

    Kod slanja zahtjeva na API moram pod Headers postaviti Content-Type: text/xml dok u Body ide XML u raw obliku.

    Na prvom API-ju dobijem popis stanica.

    http://example.com:9909/demoSoap.asmx?op=getStanice

    SOAP XML Postman

    Na drugom API-ju, nakon što pošaljem ID dvije stanice i datum, dobijem popis aktivnih stanica za odabrani datum.

    http://example.com:9909/demoSoap.asmx?op=getLinije

    SOAP XML Postman

    PHP demo aplikacija

    U službenoj dokumentaciji na adresi https://www.php.net/manual/en/class.soapclient.php saznao sam koje parametre trebam poslati kako bi dobio željeni rezultat.

    __doRequest metoda me u ovom slučaju najviše zanima.

    public SoapClient::__doRequest ( string $request , string $location , string $action , int $version [, int $one_way = 0 ] ) : string

    Iz priložene dokumentacije vidim da mi je potrebno četiri tj. pet parametara.

    getStanice

    string $request je zapravo XML koji sam u Postmanu slao kroz Body.

    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Body>
        <getStanice xmlns="http://www.tomislavstankovic.com/">
          <Korisnik>string</Korisnik>
          <Lozinka>string</Lozinka>
        </getStanice>
      </soap:Body>
    </soap:Envelope>

    string $location je URL API-ja.

    http://example.com:9909/demoSoap.asmx?op=getStanice

    string $action je potrebno pronaći u dokumentaciji API-ja.

    POST /demoSoap.asmx HTTP/1.1
    Host: tomislavstankovic.com
    Content-Type: text/xml; charset=utf-8
    Content-Length: length
    SOAPAction: "http://www.tomislavstankovic.com/getStanice"

    int $version označava verziju XML-a. U ovom slučaju to je 1.

    Sve zajedno to izgleda ovako

    <?php
    $xmlrequest = '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Body>
        <getStanice xmlns="http://www.tomislavstankovic.com/">
        <Korisnik>string</Korisnik>
        <Lozinka>string</Lozinka>
        </getStanice>
      </soap:Body>
    </soap:Envelope>';
    
    $location_URL = 'http://example.com:9909/demoSoap.asmx?op=getStanice';
    
    $client = new SoapClient(null, array(
      'method' => 'POST',
      'location' => $location_URL,
      'Content-Type' => 'Content-type: text/xml',
      'uri'      => "http://example.com:9909/demoSoap.asmx",
    ));
    
    $result = $client->__doRequest($xmlrequest, $location_URL, "http://www.tomislavstankovic.com/getStanice", 1);
    
    ?>

    Nakon što sam dobio rezultat $result trebam iz njega izvući nazive stanica tj. njihove ID-eve kako bi mogao kreirati select polja. Ovdje koristim getElementsByTagName() metodu.

    ...
    
    $dom = new DOMDocument($result);
    $dom->loadXML($result);
    
    foreach ($dom->getElementsByTagName('NewDataSet') as $sveStanice)
    {
        foreach ($sveLinije->getElementsByTagName('stanica') as $stanica)
        {
            //$popisStanica[] = $stanica; 
            $popisStanica[] = $stanica->childNodes[1]->nodeValue; 
            $popisStanica[]=$stanica->firstChild->nodeValue;
        }
       print_r($popisStanica);
    }
    
    ?>

    [nodeValue]

    print_r($popisStanica); daje sljedeći rezultat u kojemu gledam [nodeValue] unutar kojega se nalazi string sa svim podacima od pojedine stanice. Npr. [nodeValue] => idNAZIVgps1GPS2granicaZEMLJAIDnazivzemljeOZNAKA.

    Od toga sada mogu kreirati dva select polja + polje za odabir datuma.

    ...
    <form name="register" method="post">
    Početna stanica: 
    <select name="pocetnaStanica">
        <?php
            foreach($popisStanica as $key=> $pocetnaNaziv)
            {
                $id=$popisStanicaId[$key];
                echo "<option value='$id'>".$pocetnaNaziv."</option>";
            }
        ?>
    </select>
    
    Završna stanica: 
    <select name="zavrsnaStanica">
        <?php
            foreach($popisStanica as $key=> $zavrsnaNaziv)
            {
                $id=$popisStanicaId[$key];
                echo "<option value='$id'>".$zavrsnaNaziv."</option>";
            }
        ?>
    </select>
    Datum:
    <input type="date" name="datum">
    <input type="submit" name="submit" value="Pošalji"/> 
    </form>
    ?>

    Krajnji rezultat su dva padajuća izbornika sa popisom stanica.

    SOAP protokol

    getLinije

    string $request je zapravo XML koji sam u Postmanu slao kroz Body.

    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Body>
        <getLinije xmlns="http://tomislavstankovic.com/">
          <Korisnik>string</Korisnik>
          <Lozinka>string</Lozinka>
          <StanicaOd>int</StanicaOd>
          <StanicaDo>int</StanicaDo>
          <Datum>dateTime</Datum>
        </getLinije>
      </soap:Body>
    </soap:Envelope>

    Parametre StanicaOd, StanicaDo i Datum dohvaćam dinamički putem forme kroz funkciju display().

    <?php
    if(isset($_POST['submit']) && isset($_POST['pocetnaStanica']) && isset($_POST['zavrsnaStanica']) && isset($_POST['datum'])){
            display($_POST['pocetnaStanica'],$_POST['zavrsnaStanica'],$_POST['datum']);
    } else {
        echo "Odaberite vrijednosti!";
    }
    ?>

    string $location je URL API-ja.

    http://example.com:9909/demoSoap.asmx?op=getLinije

    string $action je potrebno pronaći u dokumentaciji API-ja.

    POST /demoSoap.asmx HTTP/1.1
    Host: tomislavstankovic.com
    Content-Type: text/xml; charset=utf-8
    Content-Length: length
    SOAPAction: "http://www.tomislavstankovic.com/getLinije"

    int $version označava verziju XML-a. U ovom slučaju to je 1.

    Sve zajedno to izgleda ovako

    <?php
    function display($pocetnaStanica,$zavrsnaStanica,$datum)
    {
    $xmlrequest1 = '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
    <getLinije xmlns="http://www.tomislavstankovic.com/">
     <Korisnik>string</Korisnik>
     <Lozinka>string</Lozinka>
     <StanicaOd>'.$pocetnaStanica.'</StanicaOd>
     <StanicaDo>'.$zavrsnaStanica.'</StanicaDo>
     <Datum>'.$datum.'</Datum>
    </getLinije>
    </soap:Body>
    </soap:Envelope>';
    
    $location_URL1 = 'http://example.com:9909/demoSoap.asmx?op=getLinije';
    
    $client1 = new SoapClient(null, array(
    'method' => 'POST',
    'location' => $location_URL1,
    'Content-Type' => 'Content-type: text/xml',
    'uri'      => "http://example.com:9909/demoSoap.asmx"
    ));
    
    $result1 = $client1->__doRequest($xmlrequest1, $location_URL1, "http://www.tomislavstankovic.com/getLinije", 1);
    }

    Nakon što sam dobio rezultat $result trebam iz njega izvući nazive stanica tj. njihove ID-eve, termine polaska, dolaska i ukupno trajanje putovanja kako bi mogao kreirati tablicu. Ovdje također koristim getElementsByTagName() metodu.

    ...
    
    <table class="table table-hover table-bordered thead-dark">
    <thead class="thead-dark">
      <tr>
        <th>Linija</th>
      </tr>
      </thead>
      
    
    <?php
    
    try {
      $result1 = $client1->__doRequest($xmlrequest1, $location_URL1, "http://www.tomislavstankovic.com/getLinije", 1);
      $dom1 = new DOMDocument($result1);
      $dom1->loadXML($result1);
    foreach ($dom1->getElementsByTagName('NewDataSet') as $sveLinije1)
        {
            foreach ($sveLinije1->getElementsByTagName('linija') as $linija)
            {
                //$naziviLinija[] = $linije1; 
                $naziviLinija[] = $linija->childNodes[2]->nodeValue; 
                //$vrijemePolaska[] = $linija->childNodes[3]->nodeValue;
                //$vrijemeDolaska[] = $linija->childNodes[4]->nodeValue;
                //$ukupnoTrajanje[] = $linija->childNodes[5]->nodeValue;
            }
            print_r($naziviLinija);
            foreach($naziviLinija as $nazivLinije => $value) {
    ?>
    
    <tr>
    <td><?php echo $naziviLinija[$nazivLinije]; ?></td>
      </tr>
      
    <?php
            }
        }
    } catch (SoapFault $exception){
        var_dump($exception);
    }
    }
    ?>

    [nodeValue]

    Krajnji rezultat izgleda ovako:

    SOAP post

    Prikaz vremena polaska, dolaska i trajanje mogu se pomoću jedne funkcije ljepše formatirati.

    function vrativrijeme($vrijemestring)
    {
        $x=strpos($vrijemestring,"H");
          if ($x==3) {
             $str2=substr ( $vrijemestring , $x-1, 1);
           }  else if (!$x) {
             $str2="";
           } else {
             $str2=substr ( $vrijemestring , $x-2, 2);
           } 
             $str3 = substr($vrijemestring, -3);
               $y=substr($str3,0,1);
        
          if ($y=='H') {
            $str3=substr($str3,1,1);
          } else if (!$y){
    		  $str3="";
    	  } else {
            $str3=substr($str3,0,2);
          }
    	  
    	  if (!$x) {
    		$rezultat=$str3;
            return $rezultat;
    	  } else {
    		$rezultat= $str2.":".$str3;
            return $rezultat;
    	  } 
    }
    
    ...
    
    foreach ($sveLinije1->getElementsByTagName('linija') as $linije1)
            {
                $popisLinija1[] = $linije1->childNodes[2]->nodeValue; 
                $polazak[] = vrativrijeme($linije1->childNodes[3]->nodeValue);
                $dolazak[] = vrativrijeme($linije1->childNodes[4]->nodeValue);
                $trajanje[] = vrativrijeme($linije1->childNodes[5]->nodeValue);
            }

    Krajnji rezultat sada izgleda ovako:

    SOAP post

    Zaključak

    Cilj ovog blog posta bio je pokazati kako napraviti demo PHP aplikaciju pomoću koje se može primiti/poslati XML putem SOAP protokola.

    Recenzija: CSS Grid by Wes Bos, 2. dio

    Wes Bos je poduzetnik i autor nekoliko tečajeva, među kojima je i ovaj o CSS Gridu. S obzirom da mi njegov stil podučavanja odgovara neke od tečajeva sam i kupio kako bi na strukturiran način saznao i naučio nešto novo.

    Redovito ih se sjetim kada me netko od kolega pita za dobre online resurse, a prema njihovim pozitivnim reakcijama zaključio sam da bi bilo dobro nešto više o tome napisati i na blogu.

    Jedan od tih tečajeva je CSS Grid koji je u potpunosti besplatan, a moguće ga je pronaći na adresi https://cssgrid.io/. Sastoji se od 25 videa tj. lekcija od kojih će svaka biti detaljnije pojašnjena u nastavku.

    Prvi dio blog posta objavljen pod naslovomRecenzija: CSS Grid by Wes Bos, 1. dio“.

    11.) Spanning and Placing Cardio

    U ovom dijelu tečaja cilj je ponoviti sve do sada obrađeno tj. samostalno napraviti primjer stranice sa zadanim rasmještajem elemenata.

    <body>
      <div class="container">
        <div class="item item1">1</div>
        <div class="item item2">2</div>
        <div class="item item3">3</div>
        <div class="item item4">4</div>
        <div class="item item5">5</div>
        <div class="item item6">6</div>
        <div class="item item7">7</div>
        <div class="item item8">8</div>
        <div class="item poop">💩</div>
        <div class="item item9">9</div>
        <div class="item item10">10</div>
        <div class="item item11">11</div>
        <div class="item item12">12</div>
        <div class="item item13">13</div>
        <div class="item item14">14</div>
        <div class="item item15">15</div>
        <div class="item item16">16</div>
        <div class="item item17">17</div>
        <div class="item item18">18</div>
        <div class="item item19">19</div>
        <div class="item item20">20</div>
        <div class="item item21">21</div>
        <div class="item item22">22</div>
        <div class="item item23">23</div>
        <div class="item item24">24</div>
        <div class="item item25">25</div>
        <div class="item item26">26</div>
        <div class="item item27">27</div>
        <div class="item item28">28</div>
        <div class="item item29">29</div>
        <div class="item item30">30</div>
      </div>
    
      <style>
        .container {
          display: grid;
          grid-gap: 20px;
    
          /* Make the grid 10 columns wide, every other taking up twice the free space */
          /* Make the grid have 10 explicit rows, 50px high each */
         /* With Item 1, start at col 3 and go until 5 */
         /* With Item 2, start at col 5 and go until the end */
         /* Make Item 5 double span 2 cols and rows */
         /* Make Item 8 two rows high */
         /* Make Item 15 span the entire grid width */
         /* Make item 18 span 4 widths, but end 9 */
         /* Make item 20 start at row 4 and go for 3 */
      </style>
    </body>

    Ovdje je poželjno “izgubiti” nešto vremena i barem pokušati samostalno napraviti krajnje rješenje nego u startu gledati kako ti radi Wes Bos.

    Krajnje rješenje izgleda kao u nastavku. Zakomentirani dijelovi su moji pokušaji prije nego sam ih ispravno napravio.

    .container {
          display: grid;
          grid-gap: 20px;
    
          /* Make the grid 10 columns wide, every other taking up twice the free space */
          /* grid-template-columns: repeat(10, auto 2fr); */
          /* grid-template-columns: auto 2fr auto 2fr auto 2fr auto 2fr auto 2fr; */
          grid-template-columns: repeat(5, 1fr 2fr);
    
          /* Make the grid have 10 explicit rows, 50px high each */
          /*grid-template-rows: repeat(10, 50px); */
          grid-template-rows: repeat(10, 50px); 
        }
    
        /* With Item 1, start at col 3 and go until 5 */
        .item1 {
          grid-column: 3 / 5;
        }
    
        /* With Item 2, start at col 5 and go until the end */
        .item2 {
          grid-column: 5 / -1;
        }
    
        /* Make Item 5 double span 2 cols and rows */
        .item5 {
          grid-column: span 2;
          grid-row: span 2;
        }
    
        /* Make Item 8 two rows high */
        .item8 {
          grid-row: span 2;
        }
    
        /* Make Item 15 span the entire grid width */
        .item15 {
          grid-column: 1 / -1; 
        }
    
        /* Make item 18 span 4 widths, but end 9 */
        .item18 {
          grid-column: span 4 / 9;
        }
    
        /* Make item 20 start at row 4 and go for 3 */
        .item20 {
          grid-row: 4 / span 3;
        }

    Spanning and Placing Cardio

    12.) auto-fit and auto-fill

    auto-fit i auto-fill koriste se za bolje upravljanje kolonama tj. stupcima grid-template-columns.

    Na početku imam sljedeću situaciju

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="stylesheet" href="../assets/style.css">
      <title>auto-fit and auto-fill!</title>
    </head>
    
    <body>
      <div class="container">
        <div class="item item1">Item 01</div>
        <div class="item item2">Item 02</div>
        <div class="item item3">Item 03</div>
        <div class="item item4">Item 04</div>
        <div class="item item5">Item 05</div>
        <div class="item item6">Item 06</div>
        <div class="item item7">Item 07</div>
        <div class="item item8">Item 08</div>
        <div class="item item9">Item 09</div>
        <div class="item item10">Item 10</div>
        <div class="item item11">Item 11</div>
        <div class="item item12">Item 12</div>
        <div class="item item13">Item 13</div>
        <div class="item item14">Item 14</div>
        <div class="item item15">Item 15</div>
        <div class="item item16">Item 16</div>
        <div class="item item17">Item 17</div>
        <div class="item item18">Item 18</div>
        <div class="item item19">Item 19</div>
        <div class="item item20">Item 20</div>
      </div>
    
      <style>
        .container {
          display: grid;
          grid-gap: 20px;
          border: 10px solid var(--yellow);
          grid-template-columns: repeat(5, 100px);
        }
      </style>
    </body>
    
    </html>

    Na ekranu će to izgledati kao na slici ispod. Znači, imam pet kolona i višak praznog prostora sa desne strane ekrana. U slučaju kada znam kakav će sadržaj biti u svakoj koloni tj. kada znam koliko takvih kolona mi je potrebno da bi popunio cijeli red onda mogu koristiti nešto kao grid-template-columns: repeat(5, 100px).

    auto-fit i auto-fill

    Međutim ako nisam siguran kakav će mi biti sadržaj u svakoj koloni niti koliko njih mi je potrebno da popunim cijeli red, a ipak želim da cijeli red bude dinamički popunjen mogu koristiti npr. grid-template-columns: repeat(auto-fill, 100px) tj. grid-template-columns: repeat(auto-fit, 100px) i rezultat će biti sljedeći

    auto-fit i auto-fill

    Gledajući gornju sliku lako se može zaključiti da ne postoji razlika između auto-fit i auto-fill, međutim razlika postoji i može se vidjeti kada nema dovoljno kolona da popune cijeli red.

    auto-fit završava red iza zadnje kolone – vidljivo po punoj okomitoj liniji na kraju zadnje kolone što označava explicit grid pojašnjen u prethodnom blog postu.

    .container {
          display: grid;
          grid-gap: 20px;
          border: 10px solid var(--yellow);
          grid-template-columns: repeat(auto-fit, 100px);
    }

    auto-fit i auto-fill

    auto-fill kreira prazne kolone u preostalom prostoru. U te je kolone moguće postaviti novi sadržaj.

    .container {
          display: grid;
          grid-gap: 20px;
          border: 10px solid var(--yellow);
          grid-template-columns: repeat(auto-fill, 100px);
    }

    auto-fit i auto-fill

    Npr. mogu dodati jednu kolonu u krajnji desni rub ekrana.

    .container {
          display: grid;
          grid-gap: 20px;
          border: 10px solid var(--yellow);
          grid-template-columns: repeat(auto-fill, 100px);
        }
    .item4 {
          grid-column-end: -1; 
    }

    auto-fit i auto-fill