# 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`