-
1Setup Ionic application development environment
- Ionic apps are created and developed primarily through the Ionic command line utility (the “CLI”) and use Cordova to build/deploy as a native app. Therefore, we need to install a few utilities to get developing.
- Node.js
- download and install the latest version of node.js
- Open terminal window(Mac) or command prompt window (Windows)
- install Cordova and Ionic with the following command:
npm install -g cordova ionic
Note: for Mac user can add "sudo" in front of the command depending on the system configuration
- install Cordova and Ionic with the following command:
- Create an ionic app with the following command
$ ionic start helloWorld blank
Note: "HelloWorld" will be the application's name and it is build with a blank template. There are many different types of starting template available on ionic framework.
List of templates provided by ionic:
tabs - a simple 3 tab layout
sidemenu - a layout with a swipable menu on the side
blank - a bare starter with a single page
super - starter project with over 14 ready to use page designs
tutorial - a guided starter project
- In this case the template that I am using is blank template so is easier for me to customise my UI deisgn
- Type the following command to cd into the folder that was created and run with the serve command
$ cd HelloWorld $ ionic serve
For more reference: https://ionicframework.com/docs/intro/installation/
-
2UI Design
- For UI design, I have make use of the following reference:
- Git Hub Source Code: https://github.com/yannbf/ionic3-components
- Live Demo: https://yannbf.github.io/ionic3-components/
The source code above consist a full set of ionic 3 components and sample. Therefore, I make use of the examples and design the following UI pages for my project.
Sign up Page
- User sign up with their email account and set a password
- Validation:
- Verify on email address and password with at least 6 characters.
- Email must not be the same as the existing user's username.
Login Page
- User login with their registered email account and their password
- Validation:
- validate email address (must be registered user)
- the length of the password must be 6 character
Dashboard
- Monthly Spending Category
- Users are able to view their spending amount on each of the categories and they are allowed to click on the legend to filter and click on the graph to view the exact amount spent. Information such as the Total Spending for the current month and the Current monthly Budget is shown to the user directly so they are able to capture this information quickly. The progress bar is use to monitor their budget effectively.
- There are 3 different Budgeting Status as shown below:
- No Budget is Set - the progress bar will turns into dark grey color.
- An icon of unhappy face is shown above the progress bar.
- Budget On Track - the progress bar will turns into green color and displays the percentage of the current spending amount over the budget amount set. This is to keep track of current spending amount with relation to the budget set.
- An icon of smiley face is shown above the progress bar to show that they are on track of the budget.
- Budget Exceeded - the progress bar will turns into red color and displays the percentage of the exceeded amount as compared to the budget.
- An icon of "money flying" is shown above the progress bar to show that they have overspent their budget.
- Daily Spending
- Users are allow to view their daily spending over the week from Monday to Sunday
- Monthly Spending
- Users are able to view their past 6 months's spending record, included their current month's spending, so they can compared their spending over the months.
- Edit their current month's budget
- User are able to set their current month's budget amount hence this will derive the budgeting status.
Add an expense record
- In order to create an expense record, user have to key in the following details:
- Date
- Amount Spent
- Category of the Spending
- Description
- Remark (Optional field)
Retrieve list of Expense Record
- Users are able to view the their Expense Records and display the above details and the amount spent.
- Users are also allowed to perform a search function over the description, remark and amount spent, so that they can navigate to a specific record quickly when is needed.
Update Record
- Allow users to update specific expense record.
Barcode Scanner
- Barcode Scanner which allows the user to scan the barcode on the item that they have purchased and retrieve the details. Note that currently, I only managed to find an online database that helps to manage the product data which only able to retrieve the description of the product. Therefore, other information still required the user to key in. The online database allows people across worldwide to add the product's barcode details and item details. Further details will be provided in the Code Implementation.
- For UI design, I have make use of the following reference:
-
3Set up firebase and login with firebase authentication
import { ExpenseProvider } from '../../providers/expense/expense';
I have make use of the following reference to set up my firebase: https://javebratt.com/ionic-firebase-tutorial-intro/
- Command to install firebase:
$ npm install firebase --save
- Go to the src/app/app.component.ts and import the firebase with following code:
import firebase from 'firebase/app';
- Enter into the constructor of the app.component and call the initialise app function:
firebase.initializeApp();
This function takes a config object as a parameter, and the config object has all the API keys to connect to Firebase.
- To get that config object for the app, go to the Firebase’s Console, and click on "Add Firebase to your web app". Every database will have different configuration for this.
var config = { apiKey: "", authDomain: "", databaseURL: "", projectId: "", storageBucket: "", messagingSenderId: "" };
- Enable the firebase authentication by enter into Authentication > Sign-In Method > Email and Password and enable the Status for email and password.
- Create Authentication Provider to handle the authentication related interaction between the application and the firebase. We can create multiple providers to manage the communication between the firebase and the application. Therefore, I have also created an expense Provider and Profile provider to manage the program. The following is an example of how the provider works:
- Create the Auth provider:
$ ionic generate provider Auth
- Go to the folder providers/auth/auth.ts to and add the following codes:
import {Injectable} from '@angular/core'; import firebase from 'firebase/app'; import 'firebase/auth'; import 'firebase/database'; @Injectable() export class AuthProvider { constructor() {} loginUser(email: string, password: string): Promise<any> { return firebase.auth().signInWithEmailAndPassword(email, password); } signupUser(email: string, password: string): Promise<any> { return firebase .auth() .createUserWithEmailAndPassword(email, password) .then(newUserCredential => { firebase .database() .ref(`/userProfile/${newUserCredential.user.uid}/email`) .set(email); }) .catch(error => { console.error(error); throw new Error(error); }); } resetPassword(email:string): Promise<void> { return firebase.auth().sendPasswordResetEmail(email); } logoutUser(): Promise<void> { const userId: string = firebase.auth().currentUser.uid; firebase .database() .ref(`/userProfile/${userId}`) .off(); return firebase.auth().signOut(); } }
- Explanation
- LoginUser(email:string, password:string): Promise<any> {}
- This function make use of the firebase signInWithEmailAndPassword(email, password) method, which takes in an email and password and logs the user into the application. The way that firebase works, it does not have a regular "username" login, therefore, user have to register with their valid email as the username. All the authentication error messages will be handle by firebase such as checking for existing user record when new user register check for the match record when user login.
- signupUser(email: string, password: string): Promise<any> {}
- This function takes in the email and the password from the user input and created a new user record in the firebase.
- The portion of the code .ref(`/userProfile/${newUserCredential.user.uid}/email`).set(email); is a reference to the userProfile node inside the database. The reference is creating a new node inside the userProfile node and the UID identifies the node, the UID is firebase automatic id generated for the user and it is the unique identifier. Therefore, it add a new node of "email" under every registered user.
- logoutUser():Promise<;void>{}
- This function basically check for the current user and log the specific user out.
-
4How to Create, Retrieve, Update(CRU) by using Firebase
Example on how to generate a new page:
- Generate new page Using Ionic CLI, type the following command to generate a new page in the application
- Note that ionic generate page <pageName> also works the same, the addexpense is the page name.
$ ionic g page addexpense
- This will generate 4 pages:
- addexpense.html: contains UI components for the page.
- addexpense.module.ts: for lazy loading purpose
- addexpense.scss: contains the sass styles to style the page
- addexpense.ts: contains TypeScript code (backend logic)
- Adding Pages to the Project Main Module
- Go to the src/app/app.module.ts add the following codes (Note on the comment on how the new page is added into the app):
import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; import { SplashScreen } from '@ionic-native/splash-screen'; import { StatusBar } from '@ionic-native/status-bar'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; // import the page import { AddexpensePage } from '../pages/addexpense/addexpense'; @NgModule({ declarations: [ MyApp, HomePage, // add AddexpensePage ], imports: [ BrowserModule, IonicModule.forRoot(MyApp) ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage, // add AddexpensePage ], providers: [ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler} ] }) export class AppModule {}
- To access the page, go to the app.component.ts and import the AddexpensePage and change the root page, run the program and the main page will be the AddexpensePage:
import { Component } from '@angular/core'; import { Platform } from 'ionic-angular'; import { StatusBar } from '@ionic-native/status-bar'; import { SplashScreen } from '@ionic-native/splash-screen'; import { AddexpensePage } from '../pages/addexpense/addexpense'; import firebase from 'firebase/app'; import { firebaseConfig } from './credentials'; import 'firebase/auth'; @Component({ templateUrl: 'app.html' }) export class MyApp { // change the rootPage to any pages that you want to access. root page is like home page rootPage: any = AddexpensePage constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) { firebase.initializeApp(firebaseConfig); platform.ready().then(() => { // Okay, so the platform is ready and our plugins are available. // Here you can do any higher level native things you might need. statusBar.styleDefault(); splashScreen.hide(); }); } }
After you have generate the page with the command, we can move on to implement CRU functions into our page.
Using addexpense page as a reference:
addexpense.html(UI):
<ion-header> <ion-navbar> <ion-title>Add Expense</ion-title> <ion-buttons start> <button ion-button icon-only (click)="goToDashboard()">
<ion-header> <ion-navbar> <ion-title>Add Expense</ion-title> <ion-buttons start> <button ion-button icon-only (click)="goToDashboard()"> <ion-icon ios="ios-home-outline" md="md-home-outline"></ion-icon> </button> </ion-buttons> <ion-buttons end> <button ion-button icon-only (click)="goToExpenseHistory()"> <ion-icon ios="ios-list-box-outline" md="md-list-box-outline"></ion-icon> </button> </ion-buttons> </ion-navbar> </ion-header> <ion-content padding> <form [formGroup]="addExpenseForm" (submit)="createExpense(spendDate,spentAmt,spendDesc,spendRemark)"> <ion-list> <ion-item> <ion-label color="primary">Date</ion-label> <ion-datetime displayFormat="DD/MM/YYYY" pickerFormat="DD MMM YYYY"[(ngModel)]="spendDate" formControlName="date" [class.invalid]="!addExpenseForm.controls.date.valid && blur"></ion-datetime> </ion-item> <ion-item> <ion-label color="primary">Amount</ion-label> <ion-input type = "number" placeholder="$" [(ngModel)] = "spentAmt" formControlName="amount"></ion-input> </ion-item> <ion-item> <ion-label color="primary">Category</ion-label> <ion-select [(ngModel)]= "spendCate" formControlName="category"> <ion-option value ="Food">Food</ion-option> <ion-option value ="Transport">Transport</ion-option> <ion-option value ="Shopping">Shopping</ion-option> <ion-option value ="Entertainment">Entertainment</ion-option> <ion-option value ="Education">Education</ion-option> <ion-option value ="Others">Others</ion-option> </ion-select> </ion-item> <ion-item> <ion-label color="primary">Description</ion-label> <ion-input type = "text" [(ngModel)]= "spendDesc" formControlName="desc"></ion-input> </ion-item> <ion-item> <ion-label color="primary">Remark</ion-label> <ion-input type = "text" [(ngModel)] = "spendRemark" formControlName="remark"></ion-input> </ion-item> </ion-list> <div class="error-message" *ngIf="!addExpenseForm.controls.amount.valid && addExpenseForm.controls.amount.touched" style="color:red"> Please enter a positive amount. </div> <div class="error-message" *ngIf="!addExpenseForm.controls.desc.valid && addExpenseForm.controls.desc.touched" style="color:red"> Please enter a description. </div> <div class="error-message" *ngIf="!addExpenseForm.controls.category.valid && addExpenseForm.controls.category.touched" style="color:red"> Please select a category. </div> <button ion-button block [disabled]="!addExpenseForm.valid">Add</button> </form> </ion-content>
<ion-icon ios="ios-home-outline" md="md-home-outline"></ion-icon> </button> </ion-buttons> <ion-buttons end> <button ion-button icon-only (click)="goToExpenseHistory()"> <ion-icon ios="ios-list-box-outline" md="md-list-box-outline"></ion-icon> </button> </ion-buttons> </ion-navbar> </ion-header> <ion-content padding> <form [formGroup]="addExpenseForm" (submit)="createExpense(spendDate,spentAmt,spendDesc,spendRemark)"> <ion-list> <ion-item> <ion-label color="primary">Date</ion-label> <ion-datetime displayFormat="DD/MM/YYYY" pickerFormat="DD MMM YYYY"[(ngModel)]="spendDate" formControlName="date" [class.invalid]="!addExpenseForm.controls.date.valid && blur"></ion-datetime> </ion-item> <ion-item> <ion-label color="primary">Amount</ion-label> <ion-input type = "number" placeholder="$" [(ngModel)] = "spentAmt" formControlName="amount"></ion-input> </ion-item> <ion-item> <ion-label color="primary">Category</ion-label> <ion-select [(ngModel)]= "spendCate" formControlName="category"> <ion-option value ="Food">Food</ion-option> <ion-option value ="Transport">Transport</ion-option> <ion-option value ="Shopping">Shopping</ion-option> <ion-option value ="Entertainment">Entertainment</ion-option> <ion-option value ="Education">Education</ion-option> <ion-option value ="Others">Others</ion-option> </ion-select> </ion-item> <ion-item> <ion-label color="primary">Description</ion-label> <ion-input type = "text" [(ngModel)]= "spendDesc" formControlName="desc"></ion-input> </ion-item> <ion-item> <ion-label color="primary">Remark</ion-label> <ion-input type = "text" [(ngModel)] = "spendRemark" formControlName="remark"></ion-input> </ion-item> </ion-list> <div class="error-message" *ngIf="!addExpenseForm.controls.amount.valid && addExpenseForm.controls.amount.touched" style="color:red"> Please enter a positive amount. </div> <div class="error-message" *ngIf="!addExpenseForm.controls.desc.valid && addExpenseForm.controls.desc.touched" style="color:red"> Please enter a description. </div> <div class="error-message" *ngIf="!addExpenseForm.controls.category.valid && addExpenseForm.controls.category.touched" style="color:red"> Please select a category. </div> <button ion-button block [disabled]="!addExpenseForm.valid">Add</button> </form> </ion-content>\addexpense.ts(backend logic):
import { Component } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { ExpensehistoryPage } from '../expensehistory/expensehistory'; import { ExpenseProvider } from '../../providers/expense/expense'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { DashboardPage } from '../dashboard/dashboard'; /** * Generated class for the AddexpensePage page. * * See https://ionicframework.com/docs/components/#navigation for more info on * Ionic pages and navigation. */ @IonicPage() @Component({ selector: 'page-addexpense', templateUrl: 'addexpense.html', }) export class AddexpensePage { spendCate: any; addExpenseForm: FormGroup; public errorMessage: string; constructor(public navCtrl: NavController,fb:FormBuilder,public navParams: NavParams ,public expenseProvider: ExpenseProvider) { this.addExpenseForm = fb.group({ date: [ "", Validators.compose([Validators.required]) ], amount: [ "", Validators.compose([Validators.required,Validators.pattern("^[0-9]+(\.[0-9]{1,2})?$")]) ], category: [ "", Validators.compose([Validators.required]) ], desc: [ "", Validators.compose([Validators.required]) ], remark: [ "", ] }); } formatdmy(date) { date = new Date(date); var day = ('0' + date.getDate()).slice(-2); var month = ('0' + (date.getMonth() + 1)).slice(-2); var year = date.getFullYear(); return day + '-' + month + '-' + year; } ionViewDidLoad() { console.log('ionViewDidLoad AddexpensePage'); } goToViewExpense(){ this.navCtrl.setRoot(ExpensehistoryPage); } goToDashboard(){ this.navCtrl.setRoot(DashboardPage); } goToExpenseHistory(){ this.navCtrl.setRoot(ExpensehistoryPage); } createExpense( spendDate: string, spentAmt: number, spendDesc:string, spendRemark:string, ):void { if(!this.addExpenseForm.valid){ console.log("Invalid value "); } console.log("desc is " + spendDesc); if(spendDesc == null){ spendDesc =""; } if(spendRemark == null){ spendRemark = ""; } spendDate = this.formatdmy(spendDate); this.expenseProvider .createExpense(spendDate,spentAmt,spendDesc,spendRemark,this.spendCate) .then(newExpense =>{ this.goToViewExpense() }); } }
Expense provider (in the providers/expense/expense.ts):
import { Injectable } from '@angular/core'; import firebase from 'firebase/app'; import 'firebase/auth'; import 'firebase/database'; import 'firebase/storage'; /* Generated class for the ExpenseProvider provider. See https://angular.io/guide/dependency-injection for more info on providers and Angular DI. */ @Injectable() export class ExpenseProvider { public expenseListRef: firebase.database.Reference; public expneseItem: firebase.database.Reference; public currentMonthTotalSpend = 0; public currentMonthBudget = 0; constructor() { console.log('Hello Expense Provider'); firebase.auth().onAuthStateChanged(user => { if (user) { this.expenseListRef = firebase .database() .ref(`/userProfile/${user.uid}/expenseList`); } }); } createExpense( spendDate: string, spentAmt: number, spendDesc:string, spendRemark:string, spendCate:string ): firebase.database.ThenableReference { return this.expenseListRef.push({ date: spendDate, amount: spentAmt, category:spendCate, desc: spendDesc, remark:spendRemark }); } getExpenseList(): firebase.database.Reference { return this.expenseListRef; } getExpenseDetail(expenseId:string): firebase.database.Reference { return this.expenseListRef.child(expenseId); } updateExpense(expenseId:string,date:string,amount:number,desc:string,remark:string,category:string): Promise<any> { return this.expenseListRef.child(expenseId).update({date, amount,desc,remark,category}); } }
Explanation:
- The addExpense page consist of the following input:
- Date
- use <ion-datetime></ion-datetime> tag for date
- Set the display format with property of "displayFormat" and set the date picker format with the "pickerFormat" property
- Amount
- use ion-input type of "type = amount"
- Category
- using of <ion-select></ion-select> tag and <ion-option></ion-option> to set the following options: Food, Transport, Shopping, Entertainment, Education, Others
- Description && Remark
- use ion-input type of "type = text" to store plain text
- [(ngModel)]
- Each field has a property of "[(ngModel)]" which stores the value of the input control and this value is able to pass to the backend. Therefore, this is how the front end communicate with the backend logic.
- [formGroup] property is use to set the name of the form, therefore the backend can identify the form.
- Each <form></form> tag is able to use the "(submit)" property which means when the form is submitted it will call the function from the backend.
- In this case the property of (submit)="createExpense(spendDate,spentAmt,spendDesc,spendRemark)" allows the program to pass each of the values in the ngModel to the backend function of createExpense.
- Therefore, this is how the front end communicate with the backend.
- For firebase, the communication is always managed through the provider(expense provider in this case).
- Each provider is implemented with the methods that is use to allow communicate with the data in the firebase.
- createExpense - creates/push record to the firebase
- getExpenseList - gets a list of expense records from the database
- updateExpense - allows to update the specific record with specific UID in the firebase.
- Therefore, we can see that the provider is being use in the addexpense.ts (backend) through importing of the provider:
import { ExpenseProvider } from '../../providers/expense/expense';
- The provider needs to be declare in the constructor of the class:
public expenseProvider: ExpenseProvider
- expenseProvider is then used in the method of createExpense(){} with:
this.expenseProvider .createExpense(spendDate,spentAmt,spendDesc,spendRemark,this.spendCate) .then(newExpense =>{ // do something });
There, the method above creates a communication between the front end user input and the backend logic(which communicate with the firebase through the providers).
-
5Implement Charts/Graph
- Type the following command to install the chart.js plugin:
npm install chart.js --save
- Import the chart to the page where we need to use by typing the following code:
import { Chart } from 'chart.js';
For this project, we are using the chart in the dashboard page. Therefore, we need to add the codes above into the dashboard.ts
- Add the canvas of the chart into the dashboard.html page:
<canvas #pieCanvas></canvas> <canvas #dailyBarCanvas></canvas> <canvas #barCanvas></canvas>
- pieCanvas is used for monthly spending category graph
- dailyBarCanvas is used for daily spending bar graph
- barCanvas is used for the monthly spending bar graph
- Explanation:
getBarChart() { const data = { labels: this.lastSixMonthNameArray, datasets: [{ label: 'Amount', data: this.lastSixMonthSpendingArray, backgroundColor: [ 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)' ], borderColor: [ 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)' ], borderWidth: 1 }] }; const options = { scales: { yAxes: [{ ticks: { beginAtZero: true } }] } }; return this.getChart(this.barCanvas.nativeElement, 'bar', data, options); } getDailyChart() { this.dailyBarChart = new Chart(this.dailyBarCanvas.nativeElement, { responsive: true, type: 'bar', data: { labels: this.stringDateArray, datasets: [{ label: 'Amount($)', data: this.barChartData1, backgroundColor: [ 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)' ], borderColor: [ 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)', 'rgb(88, 189, 196)' ], borderWidth: 1 }] }, options: { scales: { yAxes: [{ ticks: { beginAtZero: true } }] } } }); } getPieChart() { const data = { labels: ['Food', 'Transport', 'Shopping', 'Entertainment', 'Education', 'Others'], datasets: [ { data: [this.food, this.transport, this.shopping, this.entertainment, this.education, this.others], backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#09c1a9', '#6e49cc', '#7e7e91'], hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#09c1a9', '#6e49cc', '#7e7e91'] }] }; return this.getChart(this.pieCanvas.nativeElement, 'pie', data); }
- All the three methods above is called in the backend to fetch the data on the graph.
- In order to make the data dynamic, I make use of the two following properties:
- labels: this.lastSixMonthNameArray - which stores the dynamic labels for the graph (x axis)
- data: this.lastSixMonthSpendingArray - which stores the data for the graph(y axis).
- The above two property defined the data in the graph, therefore, steps is required to pull the data from the firebase and follows by processed the data with methods that derived the data array for the graph.
Method used to create the Spending Category Graph
In the dashboard.ts:
- GroupCategory(){} method:
groupCategory() { var today = new Date(); var month = today.getMonth(); for (var i = 0; i < this.categorySpendingList.length; i++) { var item = this.categorySpendingList[i]; var listDate = this.categorySpendingList[i].date; var formattedDate = new Date(listDate.replace(/(\d{2})-(\d{2})-(\d{4})/, "$2/$1/$3")); if (formattedDate.getMonth() == today.getMonth() && formattedDate.getFullYear() == today.getFullYear()) { if (item.category == "Food") { this.food += parseFloat(item.amount) } else if (item.category == "Transport") { this.transport += parseFloat(item.amount); } else if (item.category == "Shopping") { this.shopping += parseFloat(item.amount); } else if (item.category == "Entertainment") { this.entertainment += parseFloat(item.amount); } else if (item.category == "Education") { this.education += parseFloat(item.amount); } else if (item.category == "Others") { this.others += parseFloat(item.amount); } } } }
The method above loops through the list of the
categorySpendingList which contains the spending amount, spending date and spending category. Basically, what this method does is that:
1. It gets the data that is belong to current month and current years.
2.Then it compared the category and get the amount that is belong to the category followed by, store the amount belongs to each of the category into the variable of each category.
3. Thus, the data is then push into the array of the dataset in the piechart.
- getWeekData(){} method:
getWeekData() { this.weekList = []; var now = new Date(); var getYear = this.y2k(now.getFullYear()); var arrayLength = 0; this.getMonthListData().then(monthdata => { var transArray = monthdata; for (var i = 0; i < transArray.length; i++) { var newDate = new Date(transArray[i].date); var getDYear = this.y2k(newDate.getFullYear()); var weeknum = this.getWeek(getDYear, newDate.getMonth(), newDate.getDate()); var currentWkNum = this.getWeek(getYear, now.getMonth(), now.getDate()); var firstDayOfWeek = this.getDateOfWeek(currentWkNum, getYear); this.stringDateArray = []; var formattedfirst = this.formatdm(firstDayOfWeek); this.stringDateArray.push("Mon " + formattedfirst); for (var a = 1; a < 7; a++) { var SubDayOfWeek2 = firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 1); SubDayOfWeek2 = SubDayOfWeek2; var day = new Date(SubDayOfWeek2).getDay(); var dayInString = ""; if (day == 2) { dayInString = "Tue "; } else if (day == 3) { dayInString = "Wed "; } else if (day == 4) { dayInString = "Thu "; } else if (day == 5) { dayInString = "Fri "; } else if (day == 6) { dayInString = "Sat"; } else if (day == 0) { dayInString = "Sun"; } var formattedDate = this.formatdm(SubDayOfWeek2); this.stringDateArray.push(dayInString + formattedDate); } if (weeknum == currentWkNum) { this.weekList.push({ id: transArray[i].id, date: newDate, amount: transArray[i].amount }); } } var list = this.weekList; var mon = 0; var tue = 0; var wed = 0; var thu = 0; var fri = 0; var sat = 0; var sun = 0; var day = 0; for (var k = 0; k < list.length; k++) { day = list[k].date.getDay(); if (day == 1) { mon = mon + Number(list[k].amount); } if (day == 2) { tue = tue + Number(list[k].amount); } if (day == 3) { wed = wed + Number(list[k].amount); } if (day == 4) { thu = thu + Number(list[k].amount); } if (day == 5) { fri = fri + Number(list[k].amount); } if (day == 6) { sat = sat + Number(list[k].amount); } if (day == 7) { sun = sun + Number(list[k].amount); } } this.gmon = mon; this.gtue = tue; this.gwed = wed; this.gthu = thu; this.gfri = fri; this.gsat = sat; this.gsun = sun; this.barChartData1.push(this.gmon); this.barChartData1.push(this.gtue); this.barChartData1.push(this.gwed); this.barChartData1.push(this.gthu); this.barChartData1.push(this.gfri); this.barChartData1.push(this.gsat); this.barChartData1.push(this.gsun); for (var a = 0; a < this.barChartData1.length; a++) { this.totalWeeklySpend = Number(this.totalWeeklySpend) + Number(this.barChartData1[a]); } this.getDailyChart(); }); }
This method gets the week number base on today's date. Then it loops through the different days that is belongs to the current week and stored the data belongs to each of the day accordingly.
- getLastSixMonthArray(){} method:
getLastSixMonthArray() { // count from today's month and back for 6 months. // get the spending that is belongs to that month. // group into arrays var today = new Date(); var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var d; var month; var dismonth; // Last Six Month spending included this month var spendArray2: any[] = []; var spendNameArray: any[] = []; for (var a = 6; a >= 0; a -= 1) { d = new Date(today.getFullYear(), today.getMonth() - a, 1); var yearTobeCalculate = d.getFullYear(); month = monthNames[d.getMonth()]; var monthTobeCalculate = today.getMonth() - a; if (month == "January") { monthTobeCalculate = 0; dismonth = "Jan"; } else if (month == "February") { monthTobeCalculate = 1; dismonth = "Feb"; } else if (month == "March") { monthTobeCalculate = 2; dismonth = "Mar"; } else if (month == "April") { monthTobeCalculate = 3; dismonth = "Apr"; } else if (month == "May") { monthTobeCalculate = 4; dismonth = "May"; } else if (month == "June") { monthTobeCalculate = 5; dismonth = "Jun"; } else if (month == "July") { monthTobeCalculate = 6; dismonth = "Jul"; } else if (month == "August") { monthTobeCalculate = 7; dismonth = "Aug"; } else if (month == "September") { monthTobeCalculate = 8; dismonth = "Sep"; } else if (month == "October") { monthTobeCalculate = 9; dismonth = "Oct"; } else if (month == "November") { monthTobeCalculate = 10; dismonth = "Nov"; } else if (month == "December") { monthTobeCalculate = 11; dismonth = "Dec"; } var monthlyList: any[] = []; var spendAmt = 0; monthlyList = this.monthArrayList; for (var i = 0; i < monthlyList.length; i++) { var spendingDate = new Date(monthlyList[i].date); if (spendingDate.getFullYear() == yearTobeCalculate && spendingDate.getMonth() == monthTobeCalculate && Number(monthlyList[i].amount) > 0) { spendAmt = spendAmt + Number(monthlyList[i].amount); } } spendNameArray.push(dismonth + " " + yearTobeCalculate) spendArray2.push(spendAmt); } this.lastSixMonthNameArray = spendNameArray; this.lastSixMonthSpendingArray = spendArray2; }
What this method does is that it gets the month of today 's date. Then it counts from today's month and counts back for 6 months. Then it grab the spending that is belongs to the specific months and group into the array that stores the last six month's name and last six month's spending records.
-
6Implement Barcode Scanner
I have make reference from https://www.djamware.com/post/59bb219f80aca768e4d2b13e/example-of-ionic-3-angular-4-cordova-barcode-and-qr-code-scanner to create the barcode Scanner function.
- Type the following command to install the barcode scanner plugin and the toast plugin (to display error message)
ionic cordova plugin add phonegap-plugin-barcodescanner npm install --save @ionic-native/barcode-scanner ionic cordova plugin add cordova-plugin-x-toast npm install --save @ionic-native/toast
- Open and edit "src/app/app.module.ts" then add this imports.
import { BarcodeScanner } from '@ionic-native/barcode-scanner'; import { Toast } from '@ionic-native/toast';
- Add "BarcodeScanner" and "Toast" to "@NgModule" providers
providers:[ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}, // add here BarcodeScanner, Toast ]
- Import it into the Dashboard.ts with the following code
import { BarcodeScanner } from '@ionic-native/barcode-scanner'; import { Toast } from '@ionic-native/toast';
- Create the Scan function
With reference to the website above, the website stores the data in the local json file. Instead, i have fetch the data from a REST API: https://www.datakick.org/api. This website allows the user to add in new barcode item data into the database. Therefore, is a website to help to manage the barcode data. If any the item that the user scan is not found in the database. The user can add them manually in the database.
scan() { this.barcodeScanner.scan().then((barcodeData) => { this.dataService.getItem(barcodeData.text) .subscribe((data) => { this.products = data; if (this.products !== undefined && this.products != "") { this.productFound = true; this.productId = barcodeData.text; this.goToAddScanExpense(this.productId); } else { this.productFound = false; this.toast.show(`Product not found`, '5000', 'center').subscribe( toast => { console.log(toast); } ); } }); }, (err) => { this.toast.show(err, '5000', 'center').subscribe( toast => { console.log(toast); } ); } ); }
- Type the following command to install the barcode scanner plugin and the toast plugin (to display error message)
-
7Implement progress bar
I have make reference from the following website https://www.joshmorony.com/build-a-simple-progress-bar-component-in-ionic-2/ to create the process bar that use to keep track of the budget.
- we have to create a progress bar component with the following command:
ionic g component ProgressBar
- import the component into the src/app/app.module.ts:
import { ProgressBarComponent } from '../components/progress-bar/progress-bar';
- Add the ProgressBarComponent into the declarations under the @NgModule.
- Add the following codes into the src/components/progress-bar/progress-bar.ts
import { Component, Input } from '@angular/core'; @Component({ selector: 'progress-bar', templateUrl: 'progress-bar.html' }) export class ProgressBarComponent { @Input('progress') progress; constructor() { } }
- modify the src/components/progress-bar/progress-bar.html with the following codes:
<div style="text-align: left;"> <ion-row> <ion-col col-12> Budgeting Status: <b>{{statusMsg}}</b> <div id = "statusImgDiv"> <img id = "statusImg"class="iconItem" class="center" src="assets/imgs/budgetIcon.png" style="width: 20%"> </div> </ion-col> </ion-row> </div> <div class="progress-outer"> <div> </div> <div id = "progressbarDiv" class="progress-inner" background-color ="#33A7C3" [style.width]="progress + '%'"> {{getProgress()}}{{unit}} <!-- {{getProgress()| number: '1.0-0'}}% --> </div> </div>
- Use the component by adding the following codes into the html page that we want to use the progress bar (in this case is the dashboard.html):
<progress-bar [progress]="loadProgress"></progress-bar>
- We are also allow to add different css style property for the progress bar:
progress-bar { .progress-outer { width: 100%; padding: 3px; margin-left: -10%; text-align: center; background-color: #f4f4f4; border: 1px solid #dcdcdc; color: #fff; border-radius: 20px; } .progress-inner { min-width: 15%; white-space: nowrap; overflow: hidden; padding: 5px; border-radius: 20px; } .center { display: block; padding-top: 2%; margin-left: 25%; margin-right: 60%; width: 50%; } }
-
8Deployment
For deployment, we can choose to deploy it into iOS device or Android device. I have choose to deploy it into IOS device.
- Run the application with the following command:
ionic cordova build ios
- Open the .xcodeproj file in the platforms/ios/ from the project folder in Xcode
- Connect your phone via USB and select it as the run target
- Click on the play button in Xcode and try to run the app on your device
- More guidelines can be found from here: https://ionicframework.com/docs/intro/deploying/
- Whenever we want to update the source codes , we need to run the following command to update the codes on our device:
ionic build ios ionic cordova build ios
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.