Be insensitive, and keep a lifelong growth mindset.

0%

1. Installing Angular

Install Angular

1
2
npm install -g @angular/cli
ng

Create new Angular project

1
ng new compare-angular --routing --style=sass

Run the default project

1
2
cd compare-angular
ng serve

2. Angular Components

Generate home and faq components

1
2
ng generate component home
ng generate component home

Or for short:

1
2
ng g c home
ng g c home

Now the app components will be look like this.

Components

3. Routing & CSS Framework Integration

3.1. Routing

Edit the app-routing.moduels.ts to add Home and Faq components

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { FaqComponent} from './faq/faq.component';

const routes: Routes = [
{
path: '',
children: HomeComponent
},
{
path: 'faq',
children: FaqComponent
}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Then edit the app.component.html page as follows:

1
2
3
4
5
HEADER

<router-outlet></router-outlet>

FOOTER

Now,
http://localhost:4200 -> Home page
http://localhost:4200/faq -> Faq page

3.2. CSS Framework Integration

Install bumla

1
npm install bulma --save

Add bulma reference in .angular.cli.json

1
2
3
4
5
6
...
"styles": [
"../node_modules/bulma/bulma.sass"
"styles.sass"
],
...

Create src/mq.sass, copy and paste the same content from previous project.

Then add font-awesome to index.html file.

1
2
3
4
5
...
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
...

4.1. Header

Copy the previous header html code, and replace the HEADER in app.component.html.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div class="nav has-shadow">
<div class="container">
<div class="nav-left">
<a routerLink="/" class="nav-item">MyCompany</a>
</div>

<span class="nav-toggle">
<span></span>
<span></span>
<span></span>
</span>

<div class="nav-right nav-menu">

<a routerLink="/" class="nav-item r-item">Home</a>
<a class="nav-item r-item">About</a>
<a class="nav-item r-item">Features</a>
<a routerLink="/faq" class="nav-item r-item">FAQ</a>

<div class="nav-item">
<p class="control">
<a class="button is-primary is-outlined">
<span class="icon">
<i class="fa fa-download"></i>
</span>
<span>Join Now</span>
</a>
</p>
</div>
</div>
</div>
</div>

<router-outlet></router-outlet>

FOOTER

Then import mq.sass in app.component.sass, and copy & paste previous sass code from Vue project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@import '../mq'

.nav
background-color: #383838
a:hover
color: gray

.nav-left a
color: #fff
font-weight: bold

a.r-item
color: #C1C1C1
padding: 0.5rem 1.75rem
+mobile
color: gray
&:hover
background-color: #F1F1F1

.nav-toggle span
background-color: #C1C1C1

footer
background-color: $primary !important
color: #fff

.icon
color: #fff
margin-left: 20px

To make the menu button work, we need to make the following change in app.component.html

1
2
3
4
5
6
7
8
9
...
<span class="nav-toggle" (click)="toggleNav = !toggleNav;" [ngClass]="{'is-active': toggleNav }">
<span></span>
<span></span>
<span></span>
</span>

<div class="nav-right nav-menu" [ngClass]="{'is-active' : toggleNav }">
...

Also add “toggleNav” attribute in app.component.ts

1
2
3
4
5
...
export class AppComponent {
toggleNav: false;
}
...

Just copy and paste previous footer html code to app.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<footer class="footer is-primary">
<div class="container">
<div class="columns">
<div class="column">
<p>And this right here is a spiffy footer, where you can put stuff.</p>
</div>
<div class="column has-text-right">
<a class="icon" href="#"><i class="fa fa-facebook"></i></a>
<a class="icon" href="#"><i class="fa fa-twitter"></i></a>
</div>
</div>
</div>
</footer>

5. Home & FAQ Page

5.1. Home Page

Copy and paste the home html code to home.component.html.

Then copy and paste the previous sass code to home.component.sass as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

@import '../../mq'

.hero
background: url('/assets/clouds.jpg')
background-size: cover
animation: clouds 3s forwards

.title
+mobile
font-weight: bold
+tablet
font-size: 2.5rem
+desktop
font-size: 4rem

h2
margin: 1.5rem 0 2rem 0 !important

.fa-cog
font-size: 40px

.pd
+tablet
padding: 3.5em 0
+desktop
padding: 5em 0

5.2. Faq Page

Firstly, edit faq.component.ts as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Component({
selector: 'app-faq',
templateUrl: './faq.component.html',
styleUrls: ['./faq.component.sass']
})
export class FaqComponent implements OnInit {

faqs: Array<any>;

constructor(private http:Http) {

this.http.get('https://jsonplaceholder.typicode.com/posts')
.map(response => response.json())
.subscribe(res => this.faqs = res);
}

ngOnInit() {
}
}

The edit faq.component.html to use the first 10 items from faqs as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

<div class="container">
<section class="section">
<h1 class="title">FAQ</h1>
<h2 class="subtitle is-4">Lorum ipsum and all of that jazz.</h2>

<div class="columns">
<div class="column is-one-third" *ngFor="let faq of (faqs ? faqs.slice(0, 10): [])">
<div class="card">
<div class="card-content">
<p class="title">{{ faq.title }}</p>
<p class="answer">{{ faq.body }}</p>
</div>
</div>
</div>
</div>

</section>
</div>

Besides, we should import HttmModule in app.module.ts like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { FaqComponent } from './faq/faq.component';

@NgModule({
declarations: [
AppComponent,
HomeComponent,
FaqComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Then add the following code from previous project to faq.component.sass

1
2
3
4
5
6
7
8
9
.pd
padding: 2.5em 0 1.5em 0

.answer
margin-top: 10px !important
color: gray

.columns
flex-wrap: wrap

Everything is good now!

“运水火,应天符,合三才,然后得为之丹砂矣。妙言至径,大道至简。”


上周把即将离职的消息告诉了Manager, 婉言谢绝了老板的加薪挽留,在这里整整四年的职业生涯也进入了尾声。从刚毕业误打误撞初到硅谷的非CS专业New Graduate,到一步步能够独立负责项目,无论是Mobile还是Web,前端还是后端,都积累了不少的开发经验。在这四年的时间里,有幸遇到了非常好的老板和同事,在工作中给予了我很多的帮助,包容以及启发,同时也让我看到了不少值得我敬佩和学习的闪光点,能与你们度过这四年的时光是我的荣幸。

四年里,细数下来也算摔了不少跟头,一度陷入低谷,如今跌跌撞撞一路走来,却收之桑榆。自己在做事情的心态上开始变得更加成熟,少了些曾经年少轻狂的浮躁,更能够潜下心来,持之以恒地去坚持做一件事情。

在经济与投资理论中,复利模型往往是获取巨大利润背后的基本规律,尽管复利最初看起来往往是微不足道的,但随着时间延续,它以几何倍数增长的优势就会越发明显。而复利模型的本质无非就是做了事情A, 会导致结果B, 而有了结果B又会进一步加强A,不断往复循环。如此来说,做事情,职业和事业的发展,其背后的规律又何尝不是如此?

除了感恩于成长与收获,这四年还是有很多不足和遗憾,但归根结底,无非也就几个基本的点做得不够好。金庸小说里,武学的至高境界是无招胜有招,看似随心所欲,任意挥洒,背后却早有极深的武学功底和造诣做为基础。这样的无招变幻自如,无可预料,形式万变,让敌人无迹可寻,自可以万变破敌,可实则却是万变不离其宗,以不变应了万变。如今备受推崇的马斯克”第一性原理“其实也是如出一辙,解决好了最根本的问题,无论是做什么,都能无往而不胜。

因此,借着工作转换之际,我也思考并总结出了于我而言最重要的生活基本法则,今后不管做什么,都希望能够视之为信仰,一百年不动摇。

  • 保持成长性思维
  • 保持钝感力
  • 积极主动
  • 勤奋
  • 极简主义
  • 计划、执行、总结、反思和再计划的闭环时间管理法则
  • 坚持健身
  • 坚持写作

无论处在人生的哪个阶段,能做到坚定不移地坚持自己的信仰都是莫大的幸福。就像老人与海中,San Diego对大马林鱼的执着一样,哪怕这个信仰并不伟大,它也会因为你的热爱和坚持而高贵。

1. Installing React

Cd into the target folder, and run the following commands

1
sudo npm install -g create-react-app

Create a new react app called compare-react

1
create-react-app compare-react

Build and launch the project

1
2
cd compare-react
npm start

2. React Components

Initially, the app structure looks like this.

Initial React Components

To better organize the app components, create /src/components folder, and then create subfolders Home, Header, Footer, and Faq for each UI components.

Header.js

Same as the App.js, Header is also a stateful UI component. Create Header.js under /src/components/Header, copy and paste code from App.js, and edit it as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { Component } from 'react';

class Header extends Component {
render() {
return (
<div className="App">
MY HEADER
</div>
);
}
}

export default Header;

Footer.js

Create Footer.js under /src/components/Footer, different with Header, Footer is a stateless UI component. Instead of creating a class extending Component, we need to create a const function. See the code below.

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';

const Footer = () => {
return (
<div className = "App" >
MY FOOTER
</div>
);
};

export default Footer;

At last, import Header and Footer in App.js like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Component } from 'react';
import Header from './component/Header/Header';
import Footer from './component/Footer/Footer';
import './App.css';

class App extends Component {
render() {
return (
<div className="App">
<Header />

<Footer />
</div>
);
}
}

export default App;

Eventually, the app structure looks like this.

React Components

3. Integrating a CSS Framework

Cd project folder and install bulma

1
2
cd compare-react
npm install bulma --save

To add bulma to the project, we can reference Adding a CSS Preprocessor (Sass, Less etc.)

Edit the “scripts” section in package.json as follows:

1
2
3
4
5
6
7
8
9
"scripts": {
"build-css": "node-sass-chokidar src/ -o src/",
"watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
"start-js": "react-scripts start",
"start": "npm-run-all -p watch-css start-js",
"build": "npm run build-css && react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}

Then run the following commands

1
2
3
npm install --save-dev npm-run-all
npm install node-sass-chokidar --save-dev
npm start

Check the App.css file, and you can see that the content of App.sass has been compiled to App.css, so in our code, we can just import and reference to App.css.

The app is running with very basic header and footer now!

4. Header & Routing

4.1. Header UI

Copy and edit the header code from previous vue project to Header.js as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import React, { Component } from 'react';

class Header extends Component {
render() {
return (
<div className="App">
<div className="nav has-shadow">
<div className="container">
<div className="nav-left"
<a className="nav-item">MyCompany</a>
</div>

<span className="nav-toggle">
<span></span>
<span></span>
<span></span>
</span>

<div className="nav-right nav-menu">
<div className="nav-item">
<p className="control">
<a className="button is-primary is-outlined">
<span className="icon">
<i className="fa fa-download"></i>
</span>
<span>Join Now</span>
</a>
</p>
</div>
</div>

</div>
</div>
</div>
);
}
}

export default Header;

Copy the previous mq.sass file from vue project to /src, and create the Header.sass file under /src/components/Header with the following content.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@import '../../mq.sass'

.nav
background-color: #383838
a:hover
color: gray

.nav-left a
color: #fff
font-weight: bold

a.r-item
color: #C1C1C1
padding: 0.5rem 1.75rem
+mobile
color: gray
&:hover
background-color: #F1F1F1

.nav-toggle span
background-color: #C1C1C1

Then import ‘./Header.css’ in Header.js

Besides, we need to change the order of “import ‘./App.css’” in App.js as follows to avoid the styling of Header and Footer got overridden by App.css

1
2
3
4
5
import React, { Component } from 'react';
import './App.css';
import Header from './components/Header/Header';
import Footer from './components/Footer/Footer';
...

4.2. Header Routing

Install react-router-dom.

1
npm install react-router-dom --save

Import Link in Header.js, and add links under nav-menu div as follows.

1
2
3
4
5
6
7
8
9
10
11
12
...
import { Link } from 'react-router-dom'
...

...
<div className="nav-right nav-menu">
<Link to="/" className="nav-item r-item">Home</Link>
<Link to="/faq" className="nav-item r-item">Features</Link>
<Link to="/faq" className="nav-item r-item">About</Link>
<Link to="/faq" className="nav-item r-item">FAQ</Link>
<div className="nav-item">
...

Then edit /src/index.js to add BrowserRouter as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
, document.getElementById('root'));
registerServiceWorker();

Now, create /src/components/Home/Home.js and /src/components/Faq/Faq.js as placeholders for Home and Faq page for now.

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { Component } from 'react';

class Home extends Component {
render() {
return (
<div>
HOME
</div>
);
}
}

export default Home;
1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { Component } from 'react';

class Faq extends Component {
render() {
return (
<div>
Faq
</div>
);
}
}

export default Faq;

At last, import Home and Faq into App.js, and add Rout under Header.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';
import './App.css';
import Header from './components/Header/Header';
import Footer from './components/Footer/Footer';
import Home from './components/Home/Home';
import Faq from './components/Faq/Faq';
import { Route } from 'react-router-dom';

class App extends Component {
render() {
return (
<div className="App">
<Header />
<Route exact={true} path="/" component={Home} />
<Route path="/faq" component={Faq} />
<Footer />
</div>
);
}
}

export default App;

Now, the header navigation should just work!

Create /src/components/Footer/Footer.sass and copy the footer styling from previous VUE project.

1
2
3
4
5
6
7
8
9
@import '../../mq.sass'

footer
background-color: $primary !important
color: #fff

.icon
color: #fff
margin-left: 20px

Copy and edit the footer code from previous vue project to Footer.js as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react';
import './Footer.css'

const Footer = () => {
return (
<div>
<footer className="footer is-primary">
<div className="container">
<div className="columns">
<div className="column">
<p>And this right here is a spiffy footer, where you can put stuff.</p>
</div>
<div className="column has-text-right">
<a className="icon" href="#"><i className="fa fa-facebook"></i></a>
<a className="icon" href="#"><i className="fa fa-twitter"></i></a>
</div>
</div>
</div>
</footer>
</div>
);
};

export default Footer;

5.2. Navigation

In this section, we need to make the hamburger button work for mobile web.

Firstly, add the onClick listener for “nav-toggle” span.

1
2
3
4
5
6
7
...
<span className="nav-toggle" onClick={this.handleClick}>
<span></span>
<span></span>
<span></span>
</span>
...

Then add constructor and handleClick methods in Header class.

1
2
3
4
5
6
7
8
9
10
11
12
13
...
constructor(props) {
super(props);
this.state = {isToggleOn: false};
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
...

Besides, define a menuActive attribute in render() method.

1
2
3
4
...
render() {
let menuActive = this.state.isToggleOn ? 'is-active' : '';
...

Then modify the nav-toggle and nav-menu divs to render the menuActive attribute.

1
2
3
4
5
...
<span className={'nav-toggle ' + menuActive} onClick={this.handleClick}>
...
<div className={'nav-right nav-menu ' + menuActive}>
...

6. Home Page

Create /src/assets folder, and download the clouds.jpg there.

Create Home.sass under /src/components/Home, copy and paste the styling contents from previous VUE project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@import '../../mq.sass'

.hero
background: url('../../assets/clouds.jpg')
background-size: cover

.title
+mobile
font-weight: bold
+tablet
font-size: 2.5rem
+desktop
font-size: 4rem
margin-top: 2rem

h2
margin: 1.5rem 0 2rem 0 !important

.fa-cog
font-size: 40px

#learn
+desktop
margin-bottom: 2rem

.pd
+tablet
padding: 2em 0

Also, reference the html content from previous VUE project and update the Home.js as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import React, { Component } from 'react';
import './Home.css';

class Home extends Component {
render() {
let heading = 'Soaring to new heights';
let subheading = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
return (
<div>
<section className="hero">
<div className="hero-body">
<div className="container">
<h1 className="title">{ heading }</h1>
<div className="is-two-thirds column is-paddingless">
<h2 className="subtitle is-4">{ subheading }</h2>
</div>
<a className="button is-large is-primary" id="learn">Learn more</a>
</div>
</div>
</section>

<section className="section">
<div className="container">
<div className="columns pd is-desktop">
<div className="column is-1 has-text-centered">
<i className="fa fa-cog is-primary"></i>
</div>
<div className="column is-one-third-desktop">
<p className="title"><strong>We provide superior logistics so that your business can succeed in a crazy world.</strong></p>
</div>
<div className="column">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.</p>
</div>
</div>
</div>

<div className="columns pd">
<div className="column">
<div className="card">
<div className="card-content">
<p className="title">I think it's an absolutely excellent tool for our business. I can't survive without this thing.</p>
<p className="subtitle">- Gary Simon</p>
</div>
</div>
</div>
<div className="column">
<div className="card">
<div className="card-content">
<p className="title">I think it's an absolutely excellent tool for our business. I can't survive without this thing.</p>
<p className="subtitle">- Gary Simon</p>
</div>
</div>
</div>
<div className="column">
<div className="card">
<div className="card-content">
<p className="title">I think it's an absolutely excellent tool for our business. I can't survive without this thing.</p>
<p className="subtitle">- Gary Simon</p>
</div>
</div>
</div>
</div>
</section>
</div>
);
}
}

export default Home;

At last, delete the content in index.css to avoid the front getting affected.

Now the home page should be the same as desinged!

7. FAQ Page

We still need to use the same module as previous Vue project, so firstly install it.

1
npm install axios --save

Create Faq.sass under /src/components/Faq, copy and paste the styling contents from previous VUE project.

1
2
3
4
5
6
7
8
9
10
11
@import '../../mq.sass'

.pd
padding: 2.5em 0 1.5em 0

.answer
margin-top: 10px !important
color: gray

.columns
flex-wrap: wrap

Then edit the Faq.js as follows. Data is fetched in componentDidMount() method and saved to faqs[], and then we use {this.state.faqs.map(faq => … )} to bind data to UI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React, { Component } from 'react';
import './Faq.css';
import axios from 'axios';

class Faq extends Component {
constructor(props) {
super(props);
this.state = {
faqs: []
};
}

componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
const faqs = res.data.slice(0, 10);
this.setState({ faqs });
});
}

render() {
return (
<div>
<div className="container">
<section className="section">
<h1 className="title">FAQ</h1>
<h2 className="subtitle is-4">Lorum ipsum and all of that jazz.</h2>

<div className="columns" v-if="faqs && faqs.length">
{this.state.faqs.map(faq =>
<div className="column is-one-third" v-for="faq of faqs">
<div className="card">
<div className="card-content">
<p className="title">{ faq.title }</p>
<p className="answer">{ faq.body }</p>
</div>
</div>
</div>
)}
</div>

</section>
</div>
</div>
);
}
}

export default Faq;

Now the FAQ page is also fully implemented!

1. Installing Vue.js

3 ways to install Vue.js

  • CDN(Content Delivery Network):
  • NPM(Node Package Manager):
    • npm install vue
    • sudo npm install –global vue-cli
  • Visit nodejs.org and download an installer

Start a New Project

1
2
vue init webpack compare-vue
//webpack is the template name, and compare-vue is the project name.

Project Configuration
Project Configuration

CD into the project folder and run npm install to generate packages and jsons.

1
2
cd compare-vue
npm install

Run a project

1
npm run dev

2. Vue Components

index.html
Start point of the project, everything of the app will be inside the div with id “app”

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>compare-vue</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

src/main.js
Imports and new Vue project start.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})

App.vue
The entry point of the app, including html, script and style

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'app'
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

src/router/index.js
Define the paths of the project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import Faq from '@/components/Faq'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/faq',
name: 'Faq',
component: Faq
},
]
})

3. Integrating a CSS Framework

Install bulma in the project folder (compare-vue)

1
npm install bulma --save

Then import bulma in App.vue. To support sass, we have to add ‘lang=”sass”‘ in style tag, and also delete these “;” and “{}” under style tag.

1
2
3
4
5
6
7
8
9
10
11
12
<style lang="sass">
@import '../node_modules/bulma/bulma.sass'

#app
font-family: 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
text-align: center
color: #2c3e50
margin-top: 60px

</style>

Install additional packages to fully support sass.

1
npm install node-sass sass-loader style-loader --save-dev

Create mq.sass file under src folder with the following contents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$primary: #1EC9AC !default

// Responsiveness
// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16
$tablet: 769px !default
// 960px container + 40px
$desktop: 1000px !default
// 1152px container + 40
$widescreen: 1192px !default
// 1344px container + 40
$fullhd: 1384px !default

=mobile
@media screen and (max-width: $tablet - 1px)
@content

=tablet
@media screen and (min-width: $tablet), print
@content

=tablet-only
@media screen and (min-width: $tablet) and (max-width: $desktop - 1px)
@content

=desktop
@media screen and (min-width: $desktop)
@content

=desktop-only
@media screen and (min-width: $desktop) and (max-width: $widescreen - 1px)
@content

Then import ‘mq’ in App.vue.

1
2
3
4
5
6
7
8
9
10
11
12
13
<style lang="sass">
@import '../node_modules/bulma/bulma.sass'
@import 'mq'

#app
font-family: 'Avenir', Helvetica, Arial, sans-serif
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
text-align: center
color: #2c3e50
margin-top: 60px

</style>

Import mq in Home.vue and Faq.vue as well.

1
2
3
<style lang="sass" scoped>
@import '../mq'
</style>

4. Vue.js Navigation

Go to bulma-Doc-Navbar to see the nav bar template.

Include Font Awesome in the project index.html.

1
2
3
4
5
6
7
...
<head>
<meta charset="utf-8">
<title>compare-vue</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
...

Then edit App.vue template tag section as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

<template>
<div id="app">
<div class="nav has-shadow">
<div class="container">
<div class="nav-left">
<a class="nav-item">MyCompany</a>
</div>

<span class="nav-toggle">
<span></span>
<span></span>
<span></span>
</span>

<div class="nav-right nav-menu">
<router-link to="/" class="nav-item r-item">Home</router-link>
<router-link to="faq" class="nav-item r-item">Features</router-link>
<router-link to="faq" class="nav-item r-item">About</router-link>
<router-link to="faq" class="nav-item r-item">FAQ</router-link>
<div class="nav-item">
<p class="control">
<a class="button is-primary is-outlined">
<span class="icon">
<i class="fa fa-download"></i>
</span>
<span>Join Now</span>
</a>
</p>
</div>
</div>

</div>
</div>
<router-view></router-view>
</div>
</template>

Edit the App.vue style tag section as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style lang="sass">
@import '../node_modules/bulma/bulma.sass'
@import 'mq'

.nav
background-color: #383838
a:hover
color: gray

.nav-left a
color: #fff
font-weight: bold

a.r-item
color:#C1C1C1
padding: 0.5rem 1.75rem
+mobile
color: gray
&:hover
background-color: #F1F1F1

</style>

5. Making the Navigation Function

Add v-on:click attribute to nav-toggle and v-bind to both nav-toggle and nav-menu as follows:

1
2
3
4
5
6
7
8
9
...
<span class="nav-toggle" v-on:click="toggleNav" v-bind:class="{ 'is-active': isActive }">
...
</span>
...
<div class="nav-right nav-menu" v-bind:class="{ 'is-active': isActive }">
...
</div>
...

Edit the App.vue script tag content as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
export default {
name: 'app',
data: function() {
return {
isActive: false
}
},
methods: {
toggleNav: function() {
this.isActive = !this.isActive;
}
}
}
</script>

Add sass item in App.vue style section:

1
2
3
4
...
.nav-toggle span
background-color: #F1F1F1
...

Add footer under router-view in App.vue template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
<router-view></router-view>
<footer class="footer is-primary">
<div class="container">
<div class="columns">
<div class="column">
<p>And this right here is a spiffy footer, where you can put stuff.</p>
</div>
<div class="column has-text-right">
<a class="icon" href="#"><i class="fa fa-facebook"></i></a>
<a class="icon" href="#"><i class="fa fa-twitter"></i></a>
</div>
</div>
</div>
</footer>
...

Add styling for footer in App.vue style content

1
2
3
4
5
6
7
8
9
...
footer
background-color: $primary !important
color: #fff

.icon
color: #fff
margin-left: 20px
...

7. Hero Section

Go to bulma-Doc-Hero to see the hero template.

Edit the template tag section in Home.vue as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="home">
<section class="hero">
<div class="hero-body">
<div class="container">
<h1 class="title">{{heading}}</h1>
<div class="is-two-thirds column is-paddingless">
<h2 class="subtitle is-4">{{subheading}}</h2>
</div>
<a class="button is-large is-primary" id="learn">Learn more</a>
</div>
</div>
</section>
</div>
</template>

Edit the script tag section to return the heading and subheading attributes as follows:

1
2
3
4
5
6
7
8
9
10
11
<script>
export default {
name: 'home',
data(){
return {
heading: 'Soaring to new heights',
subheading: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
}
}
}
</script>

To add the background, firstly we need to add the clouds.jpg to src/assets folder.

Then add the sass stylings in Home.vue style tag section as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="sass" scoped>
@import '../mq'

.hero
background: url('../assets/clouds.jpg')
background-size: cover

.title
+mobile
font-weight: bold
+tablet
font-size: 2.5rem
+desktop
font-size: 4rem
margin-top: 2rem

h2
margin: 1.5rem 0 2rem 0 !important
</style>

Now the home page looks much better!
Home Page

8. Supporting Content

Add a new section below the “hero” section in Home.vue template tag section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
...
<section class="section">
<div class="container">
<div class="columns pd is-desktop">
<div class="column is-1 has-text-centered">
<i class="fa fa-cog is-primary"></i>
</div>
<div class="column is-one-third-desktop">
<p class="title"><strong>We provide superior logistics so that your business can succeed in a crazy world.</strong></p>
</div>
<div class="column">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.</p>
</div>
</div>
</div>

<div class="columns pd">
<div class="column">
<div class="card">
<div class="card-content">
<p class="title">I think it's an absolutely excellent tool for our business. I can't survive without this thing.</p>
<p class="subtitle">- Gary Simon</p>
</div>
</div>
</div>
<div class="column">
<div class="card">
<div class="card-content">
<p class="title">I think it's an absolutely excellent tool for our business. I can't survive without this thing.</p>
<p class="subtitle">- Gary Simon</p>
</div>
</div>
</div>
<div class="column">
<div class="card">
<div class="card-content">
<p class="title">I think it's an absolutely excellent tool for our business. I can't survive without this thing.</p>
<p class="subtitle">- Gary Simon</p>
</div>
</div>
</div>
</div>
</section>
</div>
</template>

Then add additional css styling in Home.vue style tag section.

1
2
3
4
5
6
7
8
9
10
.fa-cog
font-size: 40px

#learn
+desktop
margin-bottom: 2rem

.pd
+tablet
padding: 2em 0

9. FAQ Page

Install axios with the following command. (Axios is a promise based HTTP client for the browser and node.js)

1
npm install axios --save

Edit the script tag section in FAQ.vue as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import axios from 'axios';

export default {
name: 'faq',
data: () => ({
faqs: [],
errors: []
}),

created() {
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(response => {
this.faqs = response.data.slice(0, 10);
})
.catch(e => {
this.errors.push(e)
})
}
}

Then edit the template tag section in FAQ.vue as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="faq">
<div class="container">
<section class="section">
<h1 class="title">FAQ</h1>
<h2 class="subtitle is-4">Lorum ipsum and all of that jazz.</h2>

<div class="columns" v-if="faqs && faqs.length">
<div class="column is-one-third" v-for="faq of faqs">
<div class="card">
<div class="card-content">
<p class="title">{{ faq.title }}</p>
<p class="answer">{{ faq.body }}</p>
</div>
</div>
</div>
</div>

</section>
</div>
</div>
</template>

Now the FAQ page will be look like this.
FAQ page

1. Problem Statement

Suppose that you’re working in a small town administration, and you’re in charge of two town elements:

  1. Parks
  2. Streets

It’s a very small town, so right now there are only 3 parks and 4 streets. All parks and streets have a name and a build year.

At an end-of-year meeting, your boss wants a final report with the following:

  1. Tree density of each park in the town (formula: number of trees/park area);
  2. Average age of each town’s park (formula: sum of all ages/number of parks);
  3. The name of the park that has more than 1000 trees;
  4. Total and average length of the town’s streets;
  5. Size classification of all streets: tiny/small/normal/big/huge. If the size is unknown, the default is normal.

All the report data should be printed to the console.

HINT: Use some of the ES6 features: classes, subclasses, template strings, default parameters, maps, arrow functions, destructuring, etc.

2. Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class Element {
constructor (name, buildYear) {
this.name = name;
this.buildYear = buildYear;
}
}

class Park extends Element {
constructor(name, buildYear, area, numTrees) {
super(name, buildYear);
this.area = area; //km2
this.numTrees = numTrees;
}

treeDensity() {
const density = this.numTrees / this.area;
console.log('${this.name} has a tree density of ${density} trees per square km.');
}
}

class Street extends Element {
constructor(name, buildYear, length, size) {
super(name, buildYear);
this.length = length;
this.size = size;
}

classifyStreet() {
const classification = new Map();
classification.set(1, 'tiny');
classification.set(2, 'small');
classification.set(3, 'normal');
classification.set(4, 'big');
classification.set(5, 'huge');
console.log('${this.name}, build in ${this.buildYear}, is a ${classification.get(this.size)} street.');
}
}

const allParks = [new Park('Green Park', 1987, 0.2, 215), new Park('National Park', 1894, 2.9, 3541), new Park('Oak Park', 1953, 0.4, 949)];

const allStreets = [new Street('Ocean Avenue', 1999, 1.1, 4), new Street('Evergree Street', 2008, 2.7, 2), new Street('4th Street', 2015, 0.8), new Street('Sunset Boulevard', 1982, 2.5, 5)];

function calc(arr) {
const sum = arr.reduce((prev, cur, index) => prev + cur, 0);
return [sum, sum / arr.length];
}


function reportParks(p) {
console.log(----PARKS REPORTS----);
// Density
p.forEach (el -> el.treeDensity());

// Average age
const ages = p.map(el => new Date().getFullYear() - el.buildYear);
const [totalAge, avgAge] = calc(ages);
console.log('Our ${p.length} parks have an average of ${avgAge} years.');

// Which park has more than 1000 trees;
const i = p.map(el => el.numTrees).findIndex(el => el >= 1000);
console.log('${p[i].name} has more than 1000 trees.');
}

function reportStreets(s) {
console.log(---- STREETS REPORT ----);

//Total and average length of the town's streets
const [totalLength, avgLength] = calc(s.map(el => el.length));
console.log('Our ${s.length} streets have a total length of ${totalLength} km, with an average of ${avgLength} km.');

//Classify sizes
s.forEach(el => el.classifyStreet());
}

reportParks(allParks);
reportStreets(allStreets);

1. What’s new in ES6 / ES2015

1.1. JavaScript Today

  • ES 5:
    • Fully supported in all modern browsers;
    • Ready to be used today(2016)
  • ES6 / ES2015
    • Partial supported in modern browsers, no support in older browsers;
    • Can’t use it in production today (2016).
  • ES2016, ES2017, …
    • Almost no support in modern browsers;
    • Can’t use it in production today (2016)

1.2. New ES6 Features We’ll Cover in This Section

  • Variable Declarations with let and const
  • Blocks and IIFEs
  • Strings
  • Arrow Functions
  • Destructuring
  • Arrays
  • The Spread Operator
  • Rest and Default Parameters
  • Maps
  • Classes and subclasses
  • How to use ES2015 / ES6 today!

ES6 Compatibility Table

2. Variable Declarations with let and const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ES5
var name5 = 'Jane Smith';
var age5 = 23;
name5 = 'Jane Miller';
console.log(name5);

// ES6
const name6 = 'Jane Smith';
let age6 = 23;
name6 = 'Jane Miller';
console.log(name6); // will get error.

// ES5
// variables in ES5 are function scoped.
function driverLicence (passedTest) {
if (passedTest) {
var firstName = 'John';
var yearOfBirth = 1990;
}
console.log(firstName + ', born in ' + yearOfBirth + ', is now officially allowed to drive a car.');
}
driverLicence (true);

// ES6
// variables in ES6 are block scoped.
function driverLicence (passedTest) {
if (passedTest) {
var firstName = 'John';
var yearOfBirth = 1990;
}
console.log(firstName + ', born in ' + yearOfBirth + ', is now officially allowed to drive a car.'); // Will get 'firstName' not defined error.
}
driverLicence (true);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//ES5
var i = 23;
for (var i = 0; i < 5; i++) {
console.log(i);
}
console.log(i);
//it will print: 0, 1, 2, 3, 4, 5

//ES6
let i = 23;
for (let i = 0; i < 5; i++) {
console.log(i);
}
console.log(i);
//it will print: 0, 1, 2, 3, 4, 23
//It's still due to function scoped vs block scoped

3. Blocks and IIFEs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Block (IIFE) in ES6
{
const a = 1;
let b = 2;
var c = 3;
}

//console.log(a + b); //Will get error
console.log(c); // works good!


//IIFE in ES5: create block that can not be accesed outside.
(function() {
var c = 3;
})();

//console.log(c);

4. Strings in ES6 / ES2015

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let firstName = 'john';
let lastName = 'Smith';
const yearOfBirth = 1990;
function calcAge(year) {
return 2016 - year;
}

// ES5
console.log('This is ' + firstName + ' ' + lastName + '. He was born in ' + yearOfBirth + '. Today, he is ' + calcAge(yearOfBirth) + ' year old.');

//ES6
console.log('This is ${firstName} ${lastName}. He was born in ${yearOfBirth}. Today he is ${calcAge(yearOfBirth)} years old.');

const n = '${firstName} $ {lastName}';
console.log(n.startsWith('J')); //false
console.log(n.endsWith('Sm')); // false
console.log(n.includes(' ')); // true
console.log('${firstName} '.repeat(5)); //John John John John John

5. Arrow Functions: Basics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const years = [1990, 1965, 1982, 1937];

//ES5
var ages5 = years.map(function(el){
return 2016 - el;
});

console.log(ages5) //{26, 51, 34, 79}

//ES6
let ages6 = years.map(el => 2016 - el); //Exact same thing.
console.log(ages6); //{26, 51, 34, 79}

ages6 = years.map((el, index) => 'Age element ${index + 1}: ${2016 - el}.');
console.log(ages6);

ages6 = years.map((el, index) => {
const now = new Date().getFullYear();
const age = now - el;
return 'Age element ${index + 1}: ${2016 - el}.'
});
console.log(ages6);

6. Arrow Functions: Lexical ‘this’ Keyword

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//ES5
var box5 = {
color: 'green',
position: 1,
clickMe: function() {
var self = this;
document.querySelector('.green').addEventListener('click', function(){
var str = 'This is box number ' + self.position + ' and it is ' + self.color;
alert(str);
});
}
}

box5.clickMe();

//ES6
var box6 = {
color: 'green',
position: 1,
clickMe: function() {
document.querySelector('.green').addEventListener('click', () => {
var str = 'This is box number ' + this.position + ' and it is ' + this.color;
alert(str);
});
}
}

box6.clickMe();


function Person(name) {
this.name = name;
}

//ES5
Person.prototype.myFriends5 = function(friends) {
var arr = friends.map(function(el){
return this.name + ' is friends with ' + el;
}.bind(this));

console.log(arr);
}

var friends = ['Bob', 'Jane', 'Mark'];
new Person('John').myFriends5(friends);

//ES6
Person.prototype.myFriends6 = function(friends) {
var arr = friends.map(el =>
'$(this.name) is friends with ${el}');
console.log(arr);
}

new Person('Mike').myFriends6(friends);

7. Destructuring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//ES5
var john = ['John', 26];
//var name = john[0];
//var age = john[1];

//ES6
const [name, year] = ['John', 26];
console.log(name);
console.log(age);

const obj = {
firstName: 'John',
lastName: 'Smith'
};

const obj = {
firstName: 'John',
lastName: 'Smith'
};

const {firstName, lastName} = obj;
console.log(firstName);
console.log(lastName);

const {firstName: a, lastName: b} = obj;
console.log(a);
console.log(b);

function calcAgeRetirement(year) {
const age = new Date().getFullYear() - year;
return [age, 65 - age];
}

const[age2, retirement] = calcAgeRetirement(1990);
console.log(age2);
console.log(retirement);

8. Arrays in ES6 / ES2015

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const boxes = document.querySelectorAll('.box');

//ES5
var boxesArr5 = Array.prototype.slice.call(boxes);
boxesArr5.forEach(function(cur) {
cur.style.backgroundColor = 'dodgerblue';
});

//ES6
const boxesArr6 = Array.from(boxes).forEach(cur => cur.style.backgroundColor = 'dodgerblue');


//Loop
//ES5
for (var i = 0; i < boxesArr5.length; i++) {
if (boxesArr5[i].className === 'box blue') {
continue;
}
boxesArr5[i].textContent = 'I changed to blue!';
}

//ES6
for (const cur of boxesArr6) {
if (cur.className.includes('blue')) {
continue;
}
cur.textContent = 'I changed to blue!';
}

//ES5
var ages = [12, 17, 8, 21, 14, 11];
var full = ages.map(function(cur) {
return cur >= 18;
});
console.log(full);

console.log(full.indexOf(true));
console.log(ages[full.indexOf(true)]);


//ES6
console.log(ages.findIndex(cur => >= 18));
console.log(ages.find(cur => cur >= 18));

9. The Spread Operator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function addFourages (a, b, c, d) {
return a + b + c + d;
}

var sum1 = addFourAges () {18, 30, 12, 21};

console.log(sum1);

//ES5
var ages = [18, 30, 12, 21];
var sum2 = addFourAges.apply(null, ages);
console.log(sum2);

//ES6
const sum3 = addFourAges(...ages); //Spread Operator
console.log(sum3);

const familySmith = ['John', 'Jane', 'Mark'];
const familyMiller = ['Mary', 'Bob', 'Ann'];
const bigFamily = [...familySmith, 'Lily', ...familyMiller];
console.log(bigFamily);

const h = document.querySelector('h1');
const boxes = document.querySelectorAll('.box');
const all = [h, ...boxes];

Array.from(all).forEach(cur => cur.style.color = 'purple');

10. Rest Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//ES5
function isFullAge5() {
//console.log(arguments);
var argsArr = Array.prototype.slice.call(arguments);

argsArr.forEach(function(cur) {
console.log((2016 - cur) >= 18);
});
}

isFullAge5(1990, 1999, 1965);
isFullAge5(1990, 1999, 1965, 2016, 1987);

//ES6
function isFullAge6(...years) {
years.forEach(cur => console.log((2016 - cur) >= 18));
}

isFullAge6(1990, 1999, 1965, 2016, 1987);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//ES5
function isFullAge5(limit) {
console.log(arguments);
var argsArr = Array.prototype.slice.call(arguments, 1);
console.log(argsArr);

argsArr.forEach(function(cur) {
console.log((2016 - cur) >= limit);
});
}

isFullAge5(1990, 1999, 1965);
isFullAge5(1990, 1999, 1965, 2016, 1987);

//ES6
function isFullAge6(limit, ...years) {
years.forEach(cur => console.log((2016 - cur) >= limit));
}

isFullAge6(16, 1990, 1999, 1965, 2016, 1987);

11. Default Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//ES5
function SmithPerson(firstName, yerOfBirth, lastName, nationality) {
lastName === undefined? lastName = 'Smith' : lastName = lastName;
nationality === undefined ? nationality = 'american' : nationality = nationality;

this.firstName - firstName;
this.lastName = lastName;
this.yearOfBirth = yearOfBirth;
this.nationality = nationality;
}

var john = new SmithPerson('John', 1990);
var emily = new SmithPerson('Emily', 1983, 'Diaz', 'Spanish');

//ES6
function SmithPerson(firstName, yerOfBirth, lastName = 'Smith', nationality = 'american') {
this.firstName - firstName;
this.lastName = lastName;
this.yearOfBirth = yearOfBirth;
this.nationality = nationality;
}

var john = new SmithPerson('John', 1990);
var emily = new SmithPerson('Emily', 1983, 'Diaz', 'Spanish');

12. Maps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const question = new Map();
question.set('question', 'What is the official name of the latest major JavaScript version?');
question.set(1, 'ES5');
question.set(2, 'ES6');
question.set(3, 'ES2015');
question.set(4, 'ES7');
question.set('correct', 3);
question.set(true, 'Correct answer :D');
question.set(false, 'Wong, please try again!');

console.log(question.get('question'));
console.log(question.size);
if (question.has(4)) {
//question.delete(4);
console.log('Answer 4 is here!');

}
//question.clear();
question.forEach((value, key) =>
console.log("This is ${key}, and it's set to ${value}'"));

for (let [key, value] of question.entries()) {
if (typeof(key) === 'number') {
console.log('Answer ${key}: ${value}');
}
}

const ans = parseInt(promt('Write the correct answer'));
console.log(question.get(ans === question.get('correct')));

13. Classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//ES5
var Person5 = function(name, yearOfBirth, job) {
this.name = name;
this.yearOfBirth = yearOfBirth;
this.job = job;
}

Person5.prototype.calculateAge =
function() {
var age = new Date().getFullYear - this.yearOfBirth;
console.log(age);
}

var john5 = new Person5('John', 1990, 'teacher');

//ES6
class Person6 {
constructor (name, yearOfBirth, job) {
this.name = name;
this.yearOfBirth = yearOfBirth;
this.job = job;
}

calculateAge() {
var age = new Date().getFullYear - this.yearOfBirth;
console.log(age);
}

static greeting() {
console.log('Hey there!');
}
}

const john6 = new Person6('John', 1990, 'teacher');
Person6.greeting();

14. Classes with Subclasses

Inheritance In General

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//ES5
var Person5 = function(name, yearOfBirth, job) {
this.name = name;
this.yearOfBirth = yearOfBirth;
this.job = job;
}

Person5.prototype.calculateAge =
function() {
var age = new Date().getFullYear() - this.yearOfBirth;
console.log(age);
}

var Athlete5 = function(name, yearOfBirth, job, olymicGames, medals) {
Person5.call(this, name, yearOfBirth, job);
this.olymicGames = olymicGames;
this.medals = medals;
}

Athlete5.prototype.wonMedal = function () {
this.medals++;
console.log(this.medals);
}

Athlete5.prototype = Object.create(Person5.prototype);

var johnAthlete5 = new Athlete5('John', 1990, 'swimmer', 3, 10);

johnAthlete5.calculateAge();
johnAthlete5.wonMedal();


//ES6
class Person6 {
constructor (name, yearOfBirth, job) {
this.name = name;
this.yearOfBirth = yearOfBirth;
this.job = job;
}

calculateAge() {
var age = new Date().getFullYear() - this.yearOfBirth;
console.log(age);
}
}

class Athlete6 extends Perso6 {
constructor(name, yearOfBirth, job, olympicGames, medals) {
super(name, yearOfBirth, job);
this.olympicGames = olympicGames;
this.medals = medals;
}

wonMedal() {
this.medals++;
console.log(this.medals);
}
}

const johnAthlete6 = new Athlete6('John', 1990, 'swimmer', 3, 10);

johnAthlete6.wonMedal();
johnAthlete6.calculateAge();

15. How to use ES2015 / ES6 Today?

Babel: Use next generation JavaScript, today.

15.1. Environment Setup

  • Intall NodeJS
  • Install Babel
    • npm -v
    • npm install –save-dev babel-cli babel-preset-es2015 babel-polyfill

15.2. Usage

- ./node_modules/.bin/babel --presets es2015 script.js --out-file script-transpiled.js

最近又到了高考季,想想距离我自己的高考已经九年了。突然又感性了一把,翻出了当年高考结束后写的随笔。

“高三,收获的不仅仅是成绩,而是一段刻骨而难忘的征途。我需要感谢的人太多,需要铭记的事也太多。可终究还是告别了高三,我已不会再回来。”

人生是一趟永远无法回头的列车,一段征途的结束往往又是下一个大冒险的开启。尽管前面充满了泥泞和未知,但我们也不能因此而停下前行的脚步,否则就会永远地止步不前了,而没有什么比这更令人绝望。

其实很早就开始模糊地意识到,理想的人生应该是抱着一种去修行的态度,近些日子愈发坚定了这一想法。早起,极简主义,自律,健身,时间管理,成长性思维,都是在修行道路上打怪升级的有力武器。而在修行的终极目标又是什么呢?一定是要去做那些你喜欢,有意义,并且特别希望能够做成的事儿。

这世界上没有什么真正的成就是唾手可得的,否则就难以称之为成就。相对于那些上来就开挂,无限金币加血槽,一路轻松虐菜,完爆大Boss的剧情,我反倒更喜欢那种坚持了,失败,再坚持,再失败,然后还能爬起来再坚持的故事。

失败并不是一件坏事,相反,逆境的历练会让你变得更加明智和坚强。

当年J·K·罗琳因遭受家庭暴力而离婚,彼时的她距离大学毕业已经七年,没有工作,婚姻失败,还带着一个嗷嗷待哺的孩子。她自嘲为自己所见过最失败的人,但她却形容那场失败是种解放。

“失败代表了摒除不必要的事物,我不再自我欺骗、干脆忠于自我,投注所有心力完成唯一重要的工作。要是我以前在其他地方成功了,那么我也许永远不会有这样的决心,投身于这个我自信真正属于我的领域。我重获自由了!因为我最大的恐惧虽然降临了,而我还活着,我还有个可爱的女儿,还有台老旧的打字机和伟大的构思。曾经跌落深邃的谷底,却变成日后重生深厚的基础。”

无论何时何境,希望我们都能双眼带着光彩,带着一往无前的勇气,去发现最美的风景。

1. Project Planning and Architecture: Step 3

1.1. What We Just Completed

What We Just Completed

1.2. Panning: Step 3

Panning: Step 3

2. Updating the Percentages: Controller

  • What you will learn in this section:
    • Reinforcing the concepts and techniques we have learned so far.

Add updatePercentage Method in app controller.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});

document.querySelector(DOM.container).addEventListener('click', ctrlDeleteItem);

};

var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();
// 2. Return the budget
var budget = bubdgetctrl.getBudget();
// 3. Display the budget on the UI
console.log(budget);
};

var updatePercentages = function() {

// 1. Calculate percentages
// 2. Read percentages from the budget controller

// 3. Update the UI with the new percentages


};


var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 5. Calculate and update budget
updateBudget();

// 6. Calculate and update percentage
updatePercentages();
}
};

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = splitID[1];

// 1. delete the item from the data structure
budgetCtrl.deleteItem(type, ID);

// 2. Delete the item from the UI
UICtrl.deleteListItem(itemID);

// 3. Update and show the new budget
updateBudget();

// 4. Calculate and update percentage
updatePercentages();
}


};

return {
init: function() { //Our public init method
console.log('Application has started.');
UICtrl.displayBudget({
budget: 0,
totalInc: 0,
totalExp: 0,
percentage: -1
});
setupEventListeners();
}
}

})(budgetController, UIController);

3. Updating the Percentages: Budget Controller

  • What you will learn in this lecture
    • How to make our budget controller interact with the Expense prototype.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
var Expense = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
this.percentage = -1;
};

Expense.prototype.calcPercentage = function(totalIncome) {
if (totalIncome > 0) {
this.percentage = Math.round((this.value / totalIncome) * 100);
} else {
this.percentage = -1;
}
};

Expense.prototype.getPercentage = function() {
return this.percentage;
};

var Income = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var calculateTotal = function(type) {
var sum = 0;
data.allItems[type].forEach(function(cur) {
sum = sum + cur.value;
});
data.totals[type] = sum;
};

var data = {
allItems: {
exp: [],
inc: []
},
totals: {
exp: 0,
inc: 0
},
budget: 0,
percentage: -1
};

return {
addItem: function(type, des, val) {
var newItem, ID;
//Create new ID
ID = 0;
if (data.allItems[type].length > 0) {
ID = data.allItems[type][data.allItems[type].length - 1].id + 1;
} else {
ID = 0;
}

// Create new item based on 'inc' or 'exp' type
if (type === 'exp') {
newItem = new Expense(ID, des, val);
} else if (type === 'inc') {
newItem = new Income(ID, des, val);
}

//Push it into our data structure
data.allItems[type].push(newItem);

//Return the new element
return newItem;
},

deleteItem: function(type, id) {
var ids, index;
// id = 3
var ids = data.allItems[type].map(function(current) {
return current.id;
});

index = ids.indexOf(id);
if (index !=== -1) {
data.allItems[type].splice(index, 1);
}

},

calculateBudget: function() {
// calculate total income and expenses
calculateTotal('exp');
calculateTotal('inc');
// Calculate the budget: income - expenses
data.budget = data.totals.inc - data.totals.exp;

// Calculate the percentage of income that we spent
if (data.totals.inc > 0) {
data.percentage = Math.round((data.totals.exp / data.totals.inc) * 100);
} else {
data.percentage = -1;
}
},

calculatePercentages: function() {
data.allItems.exp.forEach(fuction(cur) {
cur.calcPercentage(data.totals.inc);
});
},

getPercentages: function() {
var allPerc = data.allItems.exp.map(function(cur) {
return cur.getPercentage();
});
return allPerc;
},

getBudget: function() {
return {
budget: data.budget,
totalInc: data.totals.inc,
totalExp: data.totals.exp,
percentage: data.percentage
};
},

testing: function() {
console.log(data);
}
};

})();

Now we can update our app controller:

1
2
3
4
5
6
7
8
9
10
11
12

var updatePercentages = function() {
// 1. Calculate percentages
budgetCtrl.calculatePercentages();

// 2. Read percentages from the budget controller
var percentages = budgetCtrl.getPercentages();

// 3. Update the UI with the new percentages
console.log(percentages);

};

4. Updating the Percentages: UI Controller

  • What you will learn in this section:
    • How to create our own forEach function but for nodeLists instead of arrays.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list',
budgetLabel: '.budget__value',
incomeLabeL: '.budget__income--value',
expensesLabel: '.budget__expenses--value',
percentageLabel: '.budget__expenses--percentage',
container: '.container',
expensesPercLabel: '.item__percentage'
};

return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

deleteListItem: function(selectorID) {
var el = document.getElementById(selectorID);
el.parentNode.removeChild(el);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

displayBudget: function(obj) {
document.querySelector(DOMstrings.budgetLabel).textContent = obj.budget;
document.querySelector(DOMstrings.incomeLabel).textContent = totoalInc;
document.querySelector(DOMstrings.expensesLabel).textContent = totalExp;

if(obj.percentage > 0){
document.querySelector(DOMstrings.percentageLabel).textContent = percentage + '%';
} else {
document.querySelector(DOMstrings.percentageLabel).textContent = '---';
}
},

displayPercentages: function(percentages) {
var fields = document.querySelectorAll(DOMstrings.expensesPercLabel);
var nodeListForEach = function(list, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i], i);
}
};

nodeListForEach(fields, function(current, index) {
if (percentages[index] > 0) {
current.textContent = percentages[index] + "%";
} else {
current.textContent = '---';
}
});
},

getDOMstrings: function() {
return DOMstrings;
}
};
})();

5. Formatting Our Budget Numbers: String Manipulation

  • What you will learn in this lecture
    • How to use different String methods to manipulate strings.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list',
budgetLabel: '.budget__value',
incomeLabeL: '.budget__income--value',
expensesLabel: '.budget__expenses--value',
percentageLabel: '.budget__expenses--percentage',
container: '.container',
expensesPercLabel: '.item__percentage'
};

var formatNumber = function(num, type) {
/*
+ or - before number exactly 2 decimal points
comma separating the thousands

2310.4567 => + 2,310.46
2000 -> + 2,000.00
*/

num = Math.abs(num);
num = num.toFixed(2);
numSplit = num.split('.');
int = numSplit[0];
if (int.length > 3) {
int = int.substr(0, int.length - 3) + ',' + int.substr(int.length - 3, 3);
}
dec = numSplit[1];
type === 'exp' ? sign = '-' : sign = '+';
return type + ' ' + int + '.' + dec;
};

return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', formatNumber(obj.value, type));

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

deleteListItem: function(selectorID) {
var el = document.getElementById(selectorID);
el.parentNode.removeChild(el);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

displayBudget: function(obj) {
var type;
obj.budget > 0 ? type = 'inc' : type = 'exp';
document.querySelector(DOMstrings.budgetLabel).textContent = formatNumber(obj.budget, type);
document.querySelector(DOMstrings.incomeLabel).textContent = formatNumber(totoalInc, 'inc');
document.querySelector(DOMstrings.expensesLabel).textContent = formatNumber(totalExp, 'exp');

if(obj.percentage > 0){
document.querySelector(DOMstrings.percentageLabel).textContent = percentage + '%';
} else {
document.querySelector(DOMstrings.percentageLabel).textContent = '---';
}
},

displayPercentages: function(percentages) {
var fields = document.querySelectorAll(DOMstrings.expensesPercLabel);
var nodeListForEach = function(list, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i], i);
}
};

nodeListForEach(fields, function(current, index) {
if (percentages[index] > 0) {
current.textContent = percentages[index] + "%";
} else {
current.textContent = '---';
}
});
},

getDOMstrings: function() {
return DOMstrings;
}
};
})();

6. Displaying the Current Month and Year

  • What you will learn in this lecture:
    • How to get the current date by using the Date object constructor.

Step1: add displayMonth function in UI controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list',
budgetLabel: '.budget__value',
incomeLabeL: '.budget__income--value',
expensesLabel: '.budget__expenses--value',
percentageLabel: '.budget__expenses--percentage',
container: '.container',
expensesPercLabel: '.item__percentage',
dateLabel: '.budget__title--month'
};

var formatNumber = function(num, type) {
/*
+ or - before number exactly 2 decimal points
comma separating the thousands

2310.4567 => + 2,310.46
2000 -> + 2,000.00
*/

num = Math.abs(num);
num = num.toFixed(2);
numSplit = num.split('.');
int = numSplit[0];
if (int.length > 3) {
int = int.substr(0, int.length - 3) + ',' + int.substr(int.length - 3, 3);
}
dec = numSplit[1];
type === 'exp' ? sign = '-' : sign = '+';
return type + ' ' + int + '.' + dec;
};

return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', formatNumber(obj.value, type));

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

deleteListItem: function(selectorID) {
var el = document.getElementById(selectorID);
el.parentNode.removeChild(el);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

displayBudget: function(obj) {
var type;
obj.budget > 0 ? type = 'inc' : type = 'exp';
document.querySelector(DOMstrings.budgetLabel).textContent = formatNumber(obj.budget, type);
document.querySelector(DOMstrings.incomeLabel).textContent = formatNumber(totoalInc, 'inc');
document.querySelector(DOMstrings.expensesLabel).textContent = formatNumber(totalExp, 'exp');

if(obj.percentage > 0){
document.querySelector(DOMstrings.percentageLabel).textContent = percentage + '%';
} else {
document.querySelector(DOMstrings.percentageLabel).textContent = '---';
}
},

displayPercentages: function(percentages) {
var fields = document.querySelectorAll(DOMstrings.expensesPercLabel);
var nodeListForEach = function(list, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i], i);
}
};

nodeListForEach(fields, function(current, index) {
if (percentages[index] > 0) {
current.textContent = percentages[index] + "%";
} else {
current.textContent = '---';
}
});
},

displayMonth: function() {
var now, months, month, year;
now = new Date();
// var christmas = new Date(2016, 11);

months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'Auguest', 'September', 'October', 'November', 'December'];
month = new.getMonth();

year = now.getFullYear();
document.querySelector(DOMstrings.dateLabel).textContent = months[month - 1] + ' ' + year;
},



getDOMstrings: function() {
return DOMstrings;
}
};
})();

Step2: call UICtrl.displayMonth() in init() method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});

document.querySelector(DOM.container).addEventListener('click', ctrlDeleteItem);

};

var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();
// 2. Return the budget
var budget = bubdgetctrl.getBudget();
// 3. Display the budget on the UI
console.log(budget);
};

var updatePercentages = function() {

// 1. Calculate percentages
// 2. Read percentages from the budget controller

// 3. Update the UI with the new percentages


};


var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 5. Calculate and update budget
updateBudget();

// 6. Calculate and update percentage
updatePercentages();
}
};

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = splitID[1];

// 1. delete the item from the data structure
budgetCtrl.deleteItem(type, ID);

// 2. Delete the item from the UI
UICtrl.deleteListItem(itemID);

// 3. Update and show the new budget
updateBudget();

// 4. Calculate and update percentage
updatePercentages();
}


};

return {
init: function() { //Our public init method
console.log('Application has started.');
UICtrl.displayMonth();
UICtrl.displayBudget({
budget: 0,
totalInc: 0,
totalExp: 0,
percentage: -1
});
setupEventListeners();
}
}

})(budgetController, UIController);

7. Finishing Touches: Improving the UX

  • What you will learn in this lecture
    • How and when to use ‘change’ events.

Add ‘change’ event listener in setupEventListeners function in global app controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});

document.querySelector(DOM.container).addEventListener('click', ctrlDeleteItem);
document.querySelector(DOM.inputType).addEventListener('change', UICtrl.changedType);
};

var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();
// 2. Return the budget
var budget = bubdgetctrl.getBudget();
// 3. Display the budget on the UI
console.log(budget);
};

var updatePercentages = function() {

// 1. Calculate percentages
// 2. Read percentages from the budget controller

// 3. Update the UI with the new percentages


};


var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 5. Calculate and update budget
updateBudget();

// 6. Calculate and update percentage
updatePercentages();
}
};

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = splitID[1];

// 1. delete the item from the data structure
budgetCtrl.deleteItem(type, ID);

// 2. Delete the item from the UI
UICtrl.deleteListItem(itemID);

// 3. Update and show the new budget
updateBudget();

// 4. Calculate and update percentage
updatePercentages();
}


};

return {
init: function() { //Our public init method
console.log('Application has started.');
UICtrl.displayMonth();
UICtrl.displayBudget({
budget: 0,
totalInc: 0,
totalExp: 0,
percentage: -1
});
setupEventListeners();
}
}

})(budgetController, UIController);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list',
budgetLabel: '.budget__value',
incomeLabeL: '.budget__income--value',
expensesLabel: '.budget__expenses--value',
percentageLabel: '.budget__expenses--percentage',
container: '.container',
expensesPercLabel: '.item__percentage',
dateLabel: '.budget__title--month'
};

var formatNumber = function(num, type) {
/*
+ or - before number exactly 2 decimal points
comma separating the thousands

2310.4567 => + 2,310.46
2000 -> + 2,000.00
*/

num = Math.abs(num);
num = num.toFixed(2);
numSplit = num.split('.');
int = numSplit[0];
if (int.length > 3) {
int = int.substr(0, int.length - 3) + ',' + int.substr(int.length - 3, 3);
}
dec = numSplit[1];
type === 'exp' ? sign = '-' : sign = '+';
return type + ' ' + int + '.' + dec;
};

var nodeListForEach = function(list, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i], i);
}
};

return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', formatNumber(obj.value, type));

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

deleteListItem: function(selectorID) {
var el = document.getElementById(selectorID);
el.parentNode.removeChild(el);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

displayBudget: function(obj) {
var type;
obj.budget > 0 ? type = 'inc' : type = 'exp';
document.querySelector(DOMstrings.budgetLabel).textContent = formatNumber(obj.budget, type);
document.querySelector(DOMstrings.incomeLabel).textContent = formatNumber(totoalInc, 'inc');
document.querySelector(DOMstrings.expensesLabel).textContent = formatNumber(totalExp, 'exp');

if(obj.percentage > 0){
document.querySelector(DOMstrings.percentageLabel).textContent = percentage + '%';
} else {
document.querySelector(DOMstrings.percentageLabel).textContent = '---';
}
},

displayPercentages: function(percentages) {
var fields = document.querySelectorAll(DOMstrings.expensesPercLabel);
nodeListForEach(fields, function(current, index) {
if (percentages[index] > 0) {
current.textContent = percentages[index] + "%";
} else {
current.textContent = '---';
}
});
},

displayMonth: function() {
var now, months, month, year;
now = new Date();
// var christmas = new Date(2016, 11);

months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'Auguest', 'September', 'October', 'November', 'December'];
month = new.getMonth();

year = now.getFullYear();
document.querySelector(DOMstrings.dateLabel).textContent = months[month - 1] + ' ' + year;
},

changedType: function() {
var fields = document.querySelctorAll(
DOMstrings.inputType + ',' +
DOMstrings.inputDescription + ',' +
DOMstrings.inputValue);

nodeListForEach(fields, function(cur)) {
cur.classList.toggle('red-focus');
}),
document.querySelector(DOMstrings.inputBtn).classList.toggle('red');
},

getDOMstrings: function() {
return DOMstrings;
}
};
})();

8. Our Final Architecture

Our Final Architecture

1. Project Planning and Architecture: Step 2

1.1. What We Just Completed

What We Just Completed

1.2. Panning: Step 2

Panning: Step 2

2. Event Delegation

2.1. Event Bubbling, Target Element and Event Delegation

Event Bubbling, Target Element and Event Delegation

Event bubbling mean that when an event is fired or triggered on some DOM element, for example,by clicking on our button here on the right side, then the exact same event is also triggered on all of the parent elements.

Event delegation refers to the process of using event propagation (bubbling) to handle events at a higher level in the DOM than the element on which the event originated. It allows us to attach a single event listener for elements that exist now or in the future.

2.2. Use Cases for Event Delegation

  1. When we have an element with lots of child elements that we are interested in;

  2. When we want an event handler attached to an element that is not yet in the DOM when our page is loaded.

3. Setting up the Delete Event Listener Using Event Delegation

  • What you will learn in this section:

    • How to use event delegation in practice;
    • How to use IDs in HTML to connect the UI with the data model;
    • How to use the parentNode property for DOM traversing.
  • Action Items

    • Add container in UI Controller DOMstrings;
    • Update setupEventListners in app controller;
    • Create ctrlDeleteItem method for event listener.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list',
budgetLabel: '.budget__value',
incomeLabeL: '.budget__income--value',
expensesLabel: '.budget__expenses--value',
percentageLabel: '.budget__expenses--percentage',
container: '.container'
};

return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

displayBudget: function(obj) {
document.querySelector(DOMstrings.budgetLabel).textContent = obj.budget;
document.querySelector(DOMstrings.incomeLabel).textContent = totoalInc;
document.querySelector(DOMstrings.expensesLabel).textContent = totalExp;

if(obj.percentage > 0){
document.querySelector(DOMstrings.percentageLabel).textContent = percentage + '%';
} else {
document.querySelector(DOMstrings.percentageLabel).textContent = '---';
}
},

getDOMstrings: function() {
return DOMstrings;
}
};
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});

document.querySelector(DOM.container).addEventListener('click', ctrlDeleteItem);

};

var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();
// 2. Return the budget
var budget = bubdgetctrl.getBudget();
// 3. Display the budget on the UI
console.log(budget);
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 5. Calculate and update budget
updateBudget();
}
};

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = splitID[1];

// 1. delete the item from the data structure

// 2. Delete the item from the UI

// 3. Update and show the new budget
}


};

return {
init: function() { //Our public init method
console.log('Application has started.');
UICtrl.displayBudget({
budget: 0,
totalInc: 0,
totalExp: 0,
percentage: -1
});
setupEventListeners();
}
}

})(budgetController, UIController);

4. Deleting an Item from our Budget Controller

  • What you will learn in this section:
    • Yet another method to loop over an array: map;
    • How to remove elements from an array using the splice method.

Add deleteItem Method in Budget Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
var Expense = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var Income = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var calculateTotal = function(type) {
var sum = 0;
data.allItems[type].forEach(function(cur) {
sum = sum + cur.value;
});
data.totals[type] = sum;
};

var data = {
allItems: {
exp: [],
inc: []
},
totals: {
exp: 0,
inc: 0
},
budget: 0,
percentage: -1
};

return {
addItem: function(type, des, val) {
var newItem, ID;
//Create new ID
ID = 0;
if (data.allItems[type].length > 0) {
ID = data.allItems[type][data.allItems[type].length - 1].id + 1;
} else {
ID = 0;
}

// Create new item based on 'inc' or 'exp' type
if (type === 'exp') {
newItem = new Expense(ID, des, val);
} else if (type === 'inc') {
newItem = new Income(ID, des, val);
}

//Push it into our data structure
data.allItems[type].push(newItem);

//Return the new element
return newItem;
},

deleteItem: function(type, id) {
var ids, index;
// id = 3
var ids = data.allItems[type].map(function(current) {
return current.id;
});

index = ids.indexOf(id);
if (index !=== -1) {
data.allItems[type].splice(index, 1);
}

},

calculateBudget: function() {
// calculate total income and expenses
calculateTotal('exp');
calculateTotal('inc');
// Calculate the budget: income - expenses
data.budget = data.totals.inc - data.totals.exp;

// Calculate the percentage of income that we spent
if (data.totals.inc > 0) {
data.percentage = Math.round((data.totals.exp / data.totals.inc) * 100);
} else {
data.percentage = -1;
}
},

getBudget: function() {
return {
budget: data.budget,
totalInc: data.totals.inc,
totalExp: data.totals.exp,
percentage: data.percentage
};
},

testing: function() {
console.log(data);
}
};

})();

Call deleteItem method in App Controller ctrlDeleteItem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = parseInt(splitID[1]);

// 1. delete the item from the data structure
budgetCtrl.deleteItem(type, ID);

// 2. Delete the item from the UI

// 3. Update and show the new budget
}
};

5. Deleting an Item from the UI

  • What you will learn in this section:
    • More DOM manipulation: how to remove an element from the DOM.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list',
budgetLabel: '.budget__value',
incomeLabeL: '.budget__income--value',
expensesLabel: '.budget__expenses--value',
percentageLabel: '.budget__expenses--percentage',
container: '.container'
};

return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

deleteListItem: function(selectorID) {
var el = document.getElementById(selectorID);
el.parentNode.removeChild(el);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

displayBudget: function(obj) {
document.querySelector(DOMstrings.budgetLabel).textContent = obj.budget;
document.querySelector(DOMstrings.incomeLabel).textContent = totoalInc;
document.querySelector(DOMstrings.expensesLabel).textContent = totalExp;

if(obj.percentage > 0){
document.querySelector(DOMstrings.percentageLabel).textContent = percentage + '%';
} else {
document.querySelector(DOMstrings.percentageLabel).textContent = '---';
}
},

getDOMstrings: function() {
return DOMstrings;
}
};
})();

Call UICtrl.deleteListItem and updateBudget methods in App Controller ctrlDeleteItem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

var ctrlDeleteItem = function(event) {
console.log(event.target);
var itemID, splitID;
itemID = event.target.parentNode.parentNode.parentNode.parentNode.id;
if (itemID) {
//inc-1
splitID = itemID.split("-");
type = splitID[0];
ID = parseInt(splitID[1]);

// 1. delete the item from the data structure
budgetCtrl.deleteItem(type, ID);

// 2. Delete the item from the UI
UICtrl.deleteListItem(itemID);

// 3. Update and show the new budget
updateBudget();
}
};

1. Project Planning and Architecture

1.1. Architecture Preview

The following is the final architecture preview:
Architecture Preview

But let’s not to be so fast, next we’ll start planning from scratch.

1.2. Planning

Planning

1.3. Structuring Our Code with Modules

  • Modules
    • Important aspect of any robust application’s architecture;
    • Keep the units of code for a project both cleanly separated and organized;
    • Encapsulate some data into privacy and expose other data publicly.

Structuring Out Code with Modules

2. Implementing the Module Pattern

  • What you will learn in this section:
    • How to use the module pattern;
    • More about private and public data, encapsulation and separation of concerns.

2.1. Data Encapsulation Module

1
2
3
4
5
6
7
8
9
10
11
12
var budgetController = (function() {
var x = 23;
var add = function(a) {
return x + a;
}

return {
publicTest: function(b) {
console.log(add(b));
}
}
})();

Running Result:

Data Encapsulation Module Running Result

2.2. UI Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// BUDGET CONTROLLER
var budgetController = (function() {
var x = 23;
var add = function(a) {
return x + a;
}

return {
publicTest: function(b) {
return add(b);
}
}
})();

// UI CONTROLLER
var UIController = (function() {
// Some code
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var z = budgetCtrl.publicTest(5);
return {
anotherPublic: function() {
console.log(z);
}
}
})(budgetController, UIController);

3. Setting up the First Event Listeners

  • What you will learn in this section:
    • How to set up event listeners for keypress events
    • How to use event object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
})();

// UI CONTROLLER
var UIController = (function() {
// Some code
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {

var ctrlAddItem = function() {
// 1. Get the field input data
// 2. Add the item to the budget controller
// 3. Add the item to the UI
// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
}
document.querySelector('.add_btn').addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event)) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
}
})(budgetController, UIController);

Reference:

4. Reading Input Data

  • What you will learn in this section:
    • How to read data from different HTML input types;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
};


return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},
getDOMstrings: function() {
return DOMstrings;
}
};
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var DOM = UICtrl.getDOMstrings();

var ctrlAddItem = function() {
// 1. Get the field input data
var input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
// 3. Add the item to the UI
// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
}
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event)) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
}
})(budgetController, UIController);

5. Creating an Initialization Function

  • What you will learn in this section:
    • How and why to create an initialization function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var ctrlAddItem = function() {
// 1. Get the field input data
var input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
// 3. Add the item to the UI
// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

6. Creating Income and Expense Function Constructors

  • What you will learn in this section:
    • How to choose function constructors that meet our application’s needs;
    • How to set up a proper data structure for our budget controller.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
var Expense = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var Income = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var data = {
allItems: {
exp: [],
inc: []
},
totals: {
exp: 0,
inc: 0
}
}
})();

7. Adding a New Item to Our Budget Controller

  • What you will learn in this section:
    • How to avoid conflicts in our data structures;
    • How and why to pass data from one module to another.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
var Expense = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var Income = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var data = {
allItems: {
exp: [],
inc: []
},
totals: {
exp: 0,
inc: 0
}
};

return {
addItem: function(type, des, val) {
var newItem, ID;
//Create new ID
ID = 0;
if (data.allItems[type].length > 0) {
ID = data.allItems[type][data.allItems[type].length - 1].id + 1;
} else {
ID = 0;
}

// Create new item based on 'inc' or 'exp' type
if (type === 'exp') {
newItem = new Expense(ID, des, val);
} else if (type === 'inc') {
newItem = new Income(ID, des, val);
}

//Push it into our data structure
data.allItems[type].push(newItem);

//Return the new element
return newItem;
},
testing: function() {
console.log(data);
}
};

})();


// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

8. Adding a New Item to the UI

  • What you will learn in this section:
    • A technique for adding big chunks of HTML into the DOM;
    • How to replace parts of strings;
    • How to do DOM manipulation using the insertAdjacentHTML method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list'
};

return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

getDOMstrings: function() {
return DOMstrings;
}
};
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

9. Clearing Our Input Fields

  • What you will learn in this section:
    • How to clear HTML fields;
    • How to use querySelectorAll;
    • How to convert a list to an array;
    • A better way to loop over an array then for loops: for each
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list'
};


return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

getDOMstrings: function() {
return DOMstrings;
}
};
})();


// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 4. Calculate the budget
// 5. Display the budget on the UI
console.log("It works.");
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

10. Updating the Budget: Controller

  • What you will learn in this section:
    • How to convert field inputs to numbers;
    • How to prevent false inputs

To convert a string to a number, we can use parseFloat.

1
2
3
4
5
6
7
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = parseFloat(document.querySelector(DOMstrings.inputValue).value)
};
},

Apply DRY and prevent false inputs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var updateBudget = function() {
// 1. Calculate the budget
// 2. Return the budget
// 3. Display the budget on the UI

};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 5. Calculate and update budget
updateBudget();
}
};

return {
init: function() { //Our public init method
console.log('Application has started.');
setupEventListeners();
}
}

})(budgetController, UIController);

11. Updating the Budget: Budget Controller

  • What you will learn in this section:
    • How and why to create simple, reusable functions with only one purpose;
    • How to sum all elements of an array using the forEach method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

// BUDGET CONTROLLER
var budgetController = (function() {
// Some code
var Expense = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var Income = function(id, description, value) {
this.id = id;
this.description = description;
this.value = value;
};

var calculateTotal = function(type) {
var sum = 0;
data.allItems[type].forEach(function(cur) {
sum = sum + cur.value;
});
data.totals[type] = sum;
};

var data = {
allItems: {
exp: [],
inc: []
},
totals: {
exp: 0,
inc: 0
},
budget: 0,
percentage: -1
};

return {
addItem: function(type, des, val) {
var newItem, ID;
//Create new ID
ID = 0;
if (data.allItems[type].length > 0) {
ID = data.allItems[type][data.allItems[type].length - 1].id + 1;
} else {
ID = 0;
}

// Create new item based on 'inc' or 'exp' type
if (type === 'exp') {
newItem = new Expense(ID, des, val);
} else if (type === 'inc') {
newItem = new Income(ID, des, val);
}

//Push it into our data structure
data.allItems[type].push(newItem);

//Return the new element
return newItem;
},

calculateBudget: function() {
// calculate total income and expenses
calculateTotal('exp');
calculateTotal('inc');
// Calculate the budget: income - expenses
data.budget = data.totals.inc - data.totals.exp;

// Calculate the percentage of income that we spent
if (data.totals.inc > 0) {
data.percentage = Math.round((data.totals.exp / data.totals.inc) * 100);
} else {
data.percentage = -1;
}
},

getBudget: function() {
return {
budget: data.budget,
totalInc: data.totals.inc,
totalExp: data.totals.exp,
percentage: data.percentage
};
},

testing: function() {
console.log(data);
}
};

})();

In updateBudget method:

1
2
3
4
5
6
7
8
9
10
...
var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();
// 2. Return the budget
var budget = bubdgetctrl.getBudget();
// 3. Display the budget on the UI
console.log(budget);
};
...

12. Updating the Budget: UI Controller

  • What you will learn in this section:
    • Practice DOM manipulation by updating the budget and total values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

// UI CONTROLLER
var UIController = (function() {
var DOMstrings = {
inputType: '.add__type',
inputDescription: '.add_description',
inputValue: '.add__value'
inputBtn: '.add_btn',
incomeContainer: '.income__list',
expensesContainer: '.expenses__list',
budgetLabel: '.budget__value',
incomeLabeL: '.budget__income--value',
expensesLabel: '.budget__expenses--value',
percentageLabel: '.budget__expenses--percentage'
};


return {
getInput: function() {
return {
var type = document.querySelector(DOMstrings.inputType).value,
var description = document.querySelector(DOMstrings.inputDescription).value,
var value = document.querySelector(DOMstrings.inputValue).value
};
},

addListItem: function(obj, type) {
var html, newHtml, element;
// Create HTML string with placeholder text

if (type === 'inc') {
element = DOMstrings.incomeContainer;

html = '<div class="item clearfix" id="inc-%id%"> <div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
} else if (type === 'exp') {
element = DOMstrings.expensesContainer;

html = '<div class="item clearfix" id="exp-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}

// Replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

// Insert the HTML into the DOM
document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
},

clearFields: function() {
var fields;

fields = document.querySelectorAll(DOMstrings.inputDescription + ', ' + DOMstrings.inputValue);

var fieldsArr = Array.prototype.slice.call(fields);

fieldsArr.forEach(function(current, index, array) {
current.value = "";
});

fieldsArr[0].focus();
},

displayBudget: function(obj) {
document.querySelector(DOMstrings.budgetLabel).textContent = obj.budget;
document.querySelector(DOMstrings.incomeLabel).textContent = totoalInc;
document.querySelector(DOMstrings.expensesLabel).textContent = totalExp;

if(obj.percentage > 0){
document.querySelector(DOMstrings.percentageLabel).textContent = percentage + '%';
} else {
document.querySelector(DOMstrings.percentageLabel).textContent = '---';
}
},

getDOMstrings: function() {
return DOMstrings;
}
};
})();

// GLOBAL APP CONTROLLER
var controller = (function(budgetCtrl, UICtrl) {
var setupEventListners = function() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

document.addEventListener('keypress', function(event) {
console.log(event);
if (event.keyCode === 13 || event.which === 13) {
console.log('ENTER was pressed.');
ctrlAddItem();
}
});
};

var updateBudget = function() {
// 1. Calculate the budget
budgetCtrl.calculateBudget();
// 2. Return the budget
var budget = bubdgetctrl.getBudget();
// 3. Display the budget on the UI
console.log(budget);
};

var ctrlAddItem = function() {
var input, newItem;

// 1. Get the field input data
input = UICtrl.getInput();
console.log(input);

if (input.description !== "" && !isNaN(input.value) && input.value > 0) {
// 2. Add the item to the budget controller
newItem = budgetCtrl.addItem(input.type, input.description, input.value);

// 3. Add the item to the UI
UICtrl.addListItem(newItem, input.type);

// 4. Clear the fields
UICtrl.clearFields();

// 5. Calculate and update budget
updateBudget();
}
};

return {
init: function() { //Our public init method
console.log('Application has started.');
UICtrl.displayBudget({
budget: 0,
totalInc: 0,
totalExp: 0,
percentage: -1
});
setupEventListeners();
}
}

})(budgetController, UIController);