# 31st May Report This is the 14th study report of NTUST Internship program, before going to Taiwan. This report covered all materials for "Nearby Dispenser" PWA which refers previous report. --- ## Summary of this week - Back end code for "Nearby Dispenser" system with integration to the UI design - History of this system: - Design system flowchart (10 May): https://hackmd.io/s/BkSCTiznN - Front end code (17 May): https://hackmd.io/s/BkyNpb33E - Back end code (24 May): https://hackmd.io/s/BymD5-HpE - Add filter system, device ID param from previous page (5 June) - More information see **Conclusion** at the end of this report --- ## Documentation ### 1. Nearby page back end code (nearby.page.ts) ```typescript= import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; import { ToastController } from '@ionic/angular'; @Component({ selector: 'app-nearby', templateUrl: './nearby.page.html', styleUrls: ['./nearby.page.scss'], }) export class NearbyPage implements OnInit { // API urlNearby = 'https://smartcampus.et.ntust.edu.tw:5425/Dispenser/Nearby?Device_ID='; urlDetails = 'https://smartcampus.et.ntust.edu.tw:5425/Dispenser/Detail?Device_ID='; urlPicture = 'https://smartcampus.et.ntust.edu.tw:5425/Dispenser/Image?Device_ID='; // field public nearbySameBuilding = []; public nearbyNextBuilding = []; private tempSameBuilding = []; private tempNextBuilding = []; private onlyCold : boolean = false; private onlyWarm : boolean = false; private onlyHot : boolean = false; private resultDone: boolean = false; // dummy data for test // selectedDeviceId: String = "MA_05_01"; // get deviceId from entering page selectedDeviceId: String = ""; constructor( public http: HttpClient, private route: ActivatedRoute, private router: Router, public toastCtrl: ToastController ) { /** * getting params from previous page under name "Device_ID" * any page previous of this should pass params under the same name */ this.route.queryParams.subscribe(params => { if (this.router.getCurrentNavigation().extras.state) { this.selectedDeviceId = this.router.getCurrentNavigation().extras.state.Device_ID; } }); } /** * ngOnInit() is the function that called when page being loaded. * Like in many programming, it's like main function. * * If want to use async function: * - create new function with async (ex: async myFunctionName() { } ) * - call in here with "this.myFunctionName();" */ ngOnInit() { this.main(); } /** * coldFilter() method is called when COLD button is pressed * - it will change boolean parameter for onlyCold * - it will adjust the conditionalFilter() method */ coldFilter () { if (this.resultDone) { if (!this.onlyCold) this.onlyCold = true; else this.onlyCold = false; this.conditionalFilter(); } } /** * warmFilter() method is called when WARM button is pressed * - it will change boolean parameter for onlyWarm * - it will adjust the conditionalFilter() method */ warmFilter () { if (this.resultDone) { if (!this.onlyWarm) this.onlyWarm = true; else this.onlyWarm = false; this.conditionalFilter(); } } /** * hotFilter() method is called when HOT button is pressed * - it will change boolean parameter for onlyHot * - it will adjust the conditionalFilter() method */ hotFilter () { if (this.resultDone) { if (!this.onlyHot) this.onlyHot = true; else this.onlyHot = false; this.conditionalFilter(); } } /** * Parameter needed to be mapped into page: * - Device_ID => getNearby(device_id), urlDetails(device_id) * - Status => getNearby(device_id) * - HotTemp => getNearby(device_id) * - WarmTemp => getNearby(device_id) * - ColdTemp => getNearby(device_id) * - Building => getDetails(device_id) * - BuildingLoc => getBuildingLocation (detailsJson) => for filtering * - Position => getDetails(device_id) * - Picture => getPicture(device_id) */ async main () { // check if device id is available try { let deviceAvailability_Url = this.urlDetails + this.selectedDeviceId; await this.http.get(deviceAvailability_Url).toPromise(); } catch (error) { // send Toast messsage (announce) on top of page if device id is incorrect let myToast = await this.toastCtrl.create({ message: 'Dispenser is not found or ID is incorrect!', duration: 2000, position: 'top', showCloseButton: true, closeButtonText: 'Close' }); myToast.present(); return; } // get the details of selected dispenser let currentDispenserDetails = await this.getDetails(this.selectedDeviceId); // get the location of selected dispensed let currentBuildingLocation = await this.getBuildingLocation(currentDispenserDetails); // get nearby dispensers from selected dispenser let getNearbyDispenserJson = await this.getNearby(this.selectedDeviceId); // for every dispenser in array for (let i = 0 ; i < getNearbyDispenserJson.length ; i++) { // get the dispenser ID let dispenserId = getNearbyDispenserJson[i]['Device_ID']; // get dispenser details let dispenserDetails = await this.getDetails(dispenserId); // get dispenser picture let dispenserPicture = await this.getPicture(dispenserId); // get dispenser location let dispenserBuildingLoc = await this.getBuildingLocation(dispenserDetails); // build all components into an object let tempAllDetails = { 'Device_ID': dispenserId, 'Status': getNearbyDispenserJson[i]['Status'], 'HotTemp': getNearbyDispenserJson[i]['HotTemp'], 'WarmTemp': getNearbyDispenserJson[i]['WarmTemp'], 'ColdTemp': getNearbyDispenserJson[i]['ColdTemp'], 'Building': dispenserDetails['Building'], 'Position': dispenserDetails['Position'], 'Picture': dispenserPicture }; // conditional if this dispenser is in same location with the selected dispenser if (dispenserBuildingLoc == currentBuildingLocation) { this.tempSameBuilding.push(tempAllDetails); } else { this.tempNextBuilding.push(tempAllDetails); } } // end FOR // call conditionalFilter for push from TEMP to NEARBY array field this.conditionalFilter(); } /** * this method is for getting the nearby dispenser list in Array * * @param device_id id of the dispenser * @returns myJson json of the nearby dispenser */ async getNearby (device_id) { let myUrl = this.urlNearby + device_id; let myJson = await this.http.get(myUrl).toPromise(); return myJson['Data']; } /** * this method is for getting the details of the dispenser * * @param device_id id of the dispenser * @returns myJson json of dispenser's details */ async getDetails (device_id) { let myUrl = this.urlDetails + device_id; let myJson = await this.http.get(myUrl).toPromise(); return myJson['Data']; } /** * this method is for getting the picture of the dispenser * * @param device_id id of the dispenser * * @todo: * - for now, returned value is the URL of API * - returned value should be the image * - image returned is too big (around 3000 x 4000 px) * - returned image can be optional? */ async getPicture (device_id) { let myUrl = this.urlPicture + device_id; /** * @return myImage very big image */ // let myImage = await this.http.get(myUrl).toPromise(); // return myImage; /** * @return myUrl just url of the image */ return myUrl; } /** * this method is for getting the location ID of the dispenser * ex: Device_ID = "EE_01_01", location ID = "EE" * using split function to split String value * * @param device_id id of the dispenser * @returns mbSplit[0] location ID from device ID, explained in above */ async getBuildingLocation (detailsJson) { let myBuilding = detailsJson['Device_ID']; let mbSplit = myBuilding.split("_"); return mbSplit[0]; } /** * this method is for implement either filtering or export data into nearby array field * * HOW TO DISPLAY INTO HTML: * - all data from API is stored in temp array field, named like "tempSameBuilding" * - data which displayed in HTML is from nearby array field, named like "nearbySameBuilding" * - in order to be displayed, all correspond data should be imported from TEMP to NEARBY * * HOW TO FILTERING: * - filters are divided into three categories (cold, warm, hot) yet can be selected more than one * - if filter cold is activated then any dispenser which not has cold water is discarded * - also worked when filter cold and hot is activated then the one not has cold and hot water is discarded * - using filter() function to filter current data into new data * - works three time checking, check cold first, then warm, and hot last * * resultDone variable is boolean expression for hold data not to displayed yet into HTML * - if true then data can be displayed * - vice versa for false value */ conditionalFilter () { // set resultDone to false this.resultDone = false; // import all data from temp to nearby this.nearbySameBuilding = this.tempSameBuilding; this.nearbyNextBuilding = this.tempNextBuilding; // filtering cold water dispenser if (this.onlyCold) { this.nearbySameBuilding = this.nearbySameBuilding.filter((item) => { return item['ColdTemp'] > 0; }); this.nearbyNextBuilding = this.nearbyNextBuilding.filter((item) => { return item['ColdTemp'] > 0; }); } // filtering warm water dispenser if (this.onlyWarm) { this.nearbySameBuilding = this.nearbySameBuilding.filter((item) => { return item['WarmTemp'] > 0; }); this.nearbyNextBuilding = this.nearbyNextBuilding.filter((item) => { return item['WarmTemp'] > 0; }); } // filtering hot water dispenser if (this.onlyHot) { this.nearbySameBuilding = this.nearbySameBuilding.filter((item) => { return item['HotTemp'] > 0; }); this.nearbyNextBuilding = this.nearbyNextBuilding.filter((item) => { return item['HotTemp'] > 0; }); } // set resultDone to true this.resultDone = true; } } ``` --- ### 2. Nearby page front end code (updated) #### 2.1. nearby.page.html ```htmlmixed= <ion-content> <div class="header"> <div class="overlay header-inside"> <div class="header-inside--icon-left float-left"> <img class="icon" src="assets/acuo-icons/rectangle_2@3x.png" alt="acuo-icons1" > </div> <div class="header-inside--title-center float-left"> <img class="icon avatar" src="assets\acuo-avatar\group-6@3x.png" alt="acuo-avatar1" > <h5>Nearby Water Dispenser</h5> </div> <div class="header-inside--icon-right float-left"> <img class="icon" src="assets\cancel\rectangle@3x.png" alt="acuo-cancel1" > </div> </div> </div> <div class="filter"> <div class="float-left"> <img class="filter-icon" src="assets\cup-coffee\rectangle_3@3x.png" alt="filter"> </div> <div class="filter-content float-left"> <div class="filter-content--title"> <b>I want to find...</b> </div> <div class="filter-content--options"> <div class="filter-content--options-item"> <ion-button (click)="coldFilter()" class="btn-deactived" size="small" fill="solid" expand="block" *ngIf="onlyCold == false"> Cold </ion-button> <ion-button (click)="coldFilter()" class="btn-cold-activated" size="small" fill="solid" expand="block" *ngIf="onlyCold == true"> Cold </ion-button> </div> <div class="filter-content--options-item"> <ion-button (click)="warmFilter()" class="btn-deactived" size="small" fill="solid" expand="block" *ngIf="onlyWarm == false"> Warm </ion-button> <ion-button (click)="warmFilter()" class="btn-warm-activated" size="small" fill="solid" expand="block" *ngIf="onlyWarm == true"> Warm </ion-button> </div> <div class="filter-content--options-item"> <ion-button (click)="hotFilter()" class="btn-deactived" size="small" fill="solid" expand="block" *ngIf="onlyHot == false"> Hot </ion-button> <ion-button (click)="hotFilter()" class="btn-hot-activated" size="small" fill="solid" expand="block" *ngIf="onlyHot == true"> Hot </ion-button> </div> </div> </div> </div> <div class="body"> <!-- this building --> <div class="body-content"> <h3>In this building</h3> <div class="body-content--card-inside" style="text-align: center" *ngIf="nearbySameBuilding.length == 0"> <b>No item is available</b> </div> <div *ngIf="resultDone == true"> <ion-card *ngFor="let item of nearbySameBuilding" > <div class="body-content--card"> <div class="overlay body-content--card-inside"> <div class="body-content--card-item"> <!-- dispenser icon --> <div class="float-left"> <img class="body-content--card-item--icon" src="assets\acuo-avatar\group-6@3x.png" alt="avatar"> </div> <!-- dispenser detail --> <div class="body-content--card-item--content float-left"> <div class="body-content--card-item--content-title"> {{item.Building}} <br> {{item.Position}} </div> <div class="body-content--card-item--content-options" *ngIf="item.Status == 0"> <span style="color: white; font-size: 18px"><b>Status unavailable</b></span> </div> <div class="body-content--card-item--content-options" *ngIf="item.Status != 0"> <div class="body-content--card-item--content-options-item float-left color-cold" *ngIf="item.ColdTemp > 0"> Cold </div> <div class="body-content--card-item--content-options-item float-left color-warm" *ngIf="item.WarmTemp > 0"> Warm </div> <div class="body-content--card-item--content-options-item float-left color-hot" *ngIf="item.HotTemp > 0"> Hot </div> </div> </div> </div> </div> </div> </ion-card> </div> </div> <!-- nearby building --> <div class="body-content"> <h3>In nearby building</h3> <div class="body-content--card-inside" style="text-align: center" *ngIf="nearbyNextBuilding.length == 0"> <b>No item is available</b> </div> <div *ngIf="resultDone == true"> <ion-card *ngFor="let item of nearbyNextBuilding" > <div class="body-content--card"> <div class="overlay body-content--card-inside"> <div class="body-content--card-item"> <!-- dispenser icon --> <div class="float-left"> <img class="body-content--card-item--icon" src="assets\acuo-avatar\group-6@3x.png" alt="avatar"> </div> <!-- dispenser detail --> <div class="body-content--card-item--content float-left"> <div class="body-content--card-item--content-title"> {{item.Building}} <br> {{item.Position}} </div> <div class="body-content--card-item--content-options" *ngIf="item.Status == 0"> <span style="color: white; font-size: 18px"><b>Status unavailable</b></span> </div> <div class="body-content--card-item--content-options" *ngIf="item.Status != 0"> <div class="body-content--card-item--content-options-item float-left color-cold" *ngIf="item.ColdTemp > 0"> Cold </div> <div class="body-content--card-item--content-options-item float-left color-warm" *ngIf="item.WarmTemp > 0"> Warm </div> <div class="body-content--card-item--content-options-item float-left color-hot" *ngIf="item.HotTemp > 0"> Hot </div> </div> </div> </div> </div> </div> </ion-card> </div> </div> </div> </ion-content> ``` #### 2.2. nearby.page.scss ```css= ion-content { --background: #FFFFFF; --color: #000000; } .header { background-image: url("https://blog.tiket.com/wp-content/uploads/Gambar-Pemandangan-Alam-Terindah-Danau-Toba.jpg"); background-repeat: no-repeat; background-size: cover; background-position: center center; color: #FFFFFF; overflow: auto; } .header-inside { padding: 15px; overflow: auto; } .header-inside--icon-left { width: 15%; left: 0; text-align: left; height: 100%; } .header-inside--title-center { width: 70%; text-align: center; height: 100%; padding-top: 50px; padding-bottom: 10px; } .header-inside--icon-right { width: 15%; right: 0; text-align: right; height: 100%; } .filter { background-color: #EDEDED; color: #444444; padding: 15px; overflow: auto; } .filter-icon { max-height: 20px; width: auto } .filter-content { width: 90%; padding-left: 10px } .filter-content--title { bottom: 0; left: 0; margin-bottom: 5px } .filter-content--options { top: 0; left: 0; } .filter-content--options-item { width: 30%; float: left; } .float-left { float: left; } .body { background-color: #FFFFFF; color: #666666; padding: 15px; overflow: auto; } .body-content { padding: 5px 0px 10px; } .body-content--card { background-image: url("http://thegorbalsla.com/wp-content/uploads/2018/08/Bukit-Doa-Tomohon-Manado-700x472.jpg"); background-repeat: no-repeat; background-size: cover; background-position: center center; max-height: 200px; overflow: hidden; } .body-content--card-inside { max-height: 200px; overflow: auto; padding: 15px; } .body-content--card-item { padding-top: 15px; } .body-content--card-item--icon { text-align: center; max-width: 75px; } .body-content--card-item--content { max-width: 90%; padding-left: 10px; } .body-content--card-item--content-title { bottom: 0; left: 0; margin-bottom: 5px; color: #FFFFFF } .body-content--card-item--content-options { top: 0; left: 0; } .body-content--card-item--content-options-item { border-radius: 10px; padding: 5px; margin-right: 5px; margin-bottom: 5px; text-align: center; color: white; width: 100px; } .color-cold { background-color: #40cfe5; } .color-warm { background-color: #efbe40; } .color-hot { background-color: #ff6b6b; } .icon { max-width: 100%; height: auto; } .avatar { width: 70%; max-width: 100px; } .header-title { padding-top: 50px; padding-bottom: 10px; } .overlay { z-index: 1; height: 100%; width: 100%; background-color: rgba($color: #000000, $alpha: 0.5); } .btn-deactived { --background: #bcbcbc; --color: #ffffff; } .btn-cold-activated { --background: #40cfe5; --color: #ffffff; } .btn-warm-activated { --background: #efbe40; --color: #ffffff; } .btn-hot-activated { --background: #ff6b6b; --color: #ffffff; } ``` ### 3. to-nearby #### 3.1. to-nearby.page.html ```htmlmixed= <ion-header> <ion-toolbar> <ion-title>to-nearby</ion-title> </ion-toolbar> </ion-header> <ion-content> <h3>Entering with input</h3> <ion-item> <ion-label>Enter device ID:</ion-label> <ion-input type="text" [(ngModel)]="Device_ID"></ion-input> </ion-item> <ion-button (click)="inputId()"> Go to Nearby </ion-button> <br> <h3>Entering with fixed device ID</h3> <ion-list> <ion-item> <ion-label>EE_01_01</ion-label> <ion-button (click)="EE_01_01()"> Click me </ion-button> </ion-item> <ion-item> <ion-label>D2_04_01</ion-label> <ion-button (click)="D2_04_01()"> Click me </ion-button> </ion-item> <ion-item> <ion-label>LB_04_01</ion-label> <ion-button (click)="LB_04_01()"> Click me </ion-button> </ion-item> <ion-item> <ion-label>MA_05_01</ion-label> <ion-button (click)="MA_05_01()"> Click me </ion-button> </ion-item> <ion-item> <ion-label>T4_06_01</ion-label> <ion-button (click)="T4_06_01()"> Click me </ion-button> </ion-item> </ion-list> </ion-content> ``` #### 3.2. nearby.page.ts ```typescript= import { Component, OnInit } from '@angular/core'; import { Router, NavigationExtras } from '@angular/router'; @Component({ selector: 'app-to-nearby', templateUrl: './to-nearby.page.html', styleUrls: ['./to-nearby.page.scss'], }) export class ToNearbyPage implements OnInit { private Device_ID; constructor( public router: Router ) { } ngOnInit() { } inputId () { const { Device_ID } = this; let navExtra: NavigationExtras = { state: { Device_ID: Device_ID } }; this.router.navigate(['nearby'], navExtra); } EE_01_01(){ const { Device_ID } = this; let navExtra: NavigationExtras = { state: { Device_ID: "EE_01_01" } }; this.router.navigate(['nearby'], navExtra); } D2_04_01(){ const { Device_ID } = this; let navExtra: NavigationExtras = { state: { Device_ID: "D2_04_01" } }; this.router.navigate(['nearby'], navExtra); } LB_04_01() { const { Device_ID } = this; let navExtra: NavigationExtras = { state: { Device_ID: "LB_04_01" } }; this.router.navigate(['nearby'], navExtra); } MA_05_01(){ const { Device_ID } = this; let navExtra: NavigationExtras = { state: { Device_ID: "MA_05_01" } }; this.router.navigate(['nearby'], navExtra); } T4_06_01(){ const { Device_ID } = this; let navExtra: NavigationExtras = { state: { Device_ID: "T4_06_01" } }; this.router.navigate(['nearby'], navExtra); } } ``` ### 4. Documentation #### 4.1. to-nearby page In to-nearby page, there is option of entering ID and choose ID from list <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-01.JPG" alt="1" style="max-height: 300px" /> </center> </br> #### 4.2. Enter ID and go to nearby page Entering "nearby" page with enter ID <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-02.JPG" alt="2" style="max-height: 300px" /> </center> </br> List of nearby dispenser <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-03.JPG" alt="3" style="max-height: 300px" /> </center> </br> #### 4.3. Choosing from entering via list of ID Let's choose from list <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-04.jpg" alt="4" style="max-height: 300px" /> </center> </br> List of dispenser after choosing from the list <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-05.JPG" alt="5" style="max-height: 300px" /> </center> </br> Continue of nearby dispenser list... <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-06.JPG" alt="6" style="max-height: 300px" /> </center> </br> #### 4.4. Using filter system Before implement filter <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-07.JPG" alt="7" style="max-height: 300px" /> </center> </br> After implement filter <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-08.JPG" alt="8" style="max-height: 300px" /> </center> </br> #### 4.5. Wrong input If wrong device id <center> <img src="https://raw.githubusercontent.com/aru1702/images/master/ntust-documentation/14-09.JPG" alt="9" style="max-height: 300px" /> </center> </br> --- ## Conclusion: - This page displaying all listed nearby dispenser from API - It displaying the availability of water, location, and picture - Using passed parameter from previous page to load which selected dispenser as reference - All data from API are stored in local array field, then displayed into HTML - For filter system, it filter from the array and re-displayed to HTML - If no dispenser is found, HTML will display "No item is available" - If referenced dispenser is not found, HTML will create Toast (like announcement) tell that "Dispenser is not found or ID is incorrect!" - If dispenser is found but reported status is zero (means that API cannot retrieve the data), it will display the dispenser but no data for water availability **Future work:** - Image from API is too large (about 3000 x 4000 px) therefore the image is not able to be called in HTML (waste of resources, internet quota) - Image URL is stored in array field, if user wants to see it must click the item and a pop up will display the image - Background image for title and each dispenser in the list still using free internet image (from Google), need to store local example image for these ###### tags: `pre-intern report`