vuenews-practice

Last week, I created a news applicaiton by using Vue.js and ElementUI

Install Vue

Install Vue.js by using the cli.

See the document here

Install ElementUI

Install ElementUI. See the document here

Apply an API key from News API

Apply from here

Install Vue-axios

Vue-axios is a tool for sending get request in our application.

Follow the document here

Install vue-lazyload

A Vue.js plugin for lazyload your Image or Component in the application.

Install from here

Install Vuex

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. It also integrates with Vue’s official devtools extension to provide advanced features such as zero-config time-travel debugging and state snapshot export / import.

1
npm install vuex --save

Explicitly install Vuex via Vue.use():

1
2
3
4
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

Then in the src/main.js file. Create a store variable

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
/* eslint-disable no-new */
const store = new Vuex.Store({
state:{
// store the selected source which is the current source
selectedSource:null,
// store all sources fetched from news api
sources:null,
// the layout if false, layout is list
grid:true
},
mutations:{
updateSelectedSource(state,newSource){
state.selectedSource = newSource;
},
updateSources(state,sources){
state.sources = sources;
},
changeLayout(state){
state.grid = !state.grid;
}
},
actions:{
update_selected_source(store,newSource){
store.commit('updateSelectedSource',newSource);
},
update_sources(store,sources){
store.commit('updateSources',sources);
},
change_layout(store){
store.commit('changeLayout');
}
}
})

SourceList components

Create a file named ‘SourceList.vue’ under the /src/components folder.

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
<template>
<el-row :gutter="20">
<el-col :span="18" style="margin-top: 10px" :offset='7'>
<el-form :inline="true">
<el-form-item label="News sources">
<el-select v-model="selectedSource" placeholder="Please select a source">
<el-option-group
v-for="source in sources"
:key="source.label"
:label="source.label">
<el-option
v-for="item in source.options"
:key="item.name"
:label="item.name"
:value="item">
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item>
<el-tooltip class="item" effect="light" placement="bottom-start" v-if="selectedSource">
<div slot="content">{{ selectedSource.description }}</div>
<el-button type="primary"><a v-bind:href="selectedSource.url" target="_blank">Go to {{ selectedSource.name }}</a></el-button>
</el-tooltip>
</el-form-item>
<el-form-item v-if="selectedSource">
<el-button type="primary" icon="menu" @click="changeLayout">Change Layout</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>

<script>
export default {
name: 'sourcelist',
watch:{
'selectedSource':'changeSource'
},
data(){
return{
selectedSource:null,
}
},
computed:{
sources(){
return this.$store.state.sources;
}
},
methods:{
changeSource(){ this.$store.dispatch('update_selected_source',this.selectedSource);
},
changeLayout(){
this.$store.dispatch('change_layout');
}
}

}
</script>

<style scoped>
a{
text-decoration: none;
color:white;
}
</style>

NewsList component

Create a file named ‘NewsList.vue’ under the /src/components folder.

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
<template>

<el-row :gutter="20" v-loading.fullscreen.lock="loading">
<el-col :span="span" :offset="offset" v-for="news in newsList" :key="news.title">
<el-card :body-style="{ padding: '5px'}" style="height:500px;margin-bottom:5px" >
<img v-lazy="news.urlToImage ? news.urlToImage : 'http://via.placeholder.com/350x300?text=No+image+available' " style="width:100%;height:300px;"/>

<div style="padding: 14px;">
<span>{{ news.title }}</span>
<div v-if="news.description" class="wrapper">
<p class="description" >{{ news.description }}</p>
</div>
<div class="bottom clearfix">
<time class="time">{{ news.publishedAt }}</time>
<el-button type="text" class="button" ><a v-bind:href="news.url" target="_blank">View</a></el-button>
</div>
</div>
</el-card>
</el-col>
</el-row>
</template>

<script>
export default {
name: 'newslist',
data(){
return{
newsList:[],
loading:false,
span: 8,
offset: 0
}
},
created(){
this.fetchNews();
},
watch:{
'source':'fetchNews',
'grid':'changeLayout'
},
computed:{
source(){
return this.$store.state.selectedSource;
},
grid(){
return this.$store.state.grid;
}
},
methods:{
fetchNews(){
console.log('fetch invoked...');
if(this.source)
{
this.loading = true;
console.log('fetching...');
this.axios.get('https://newsapi.org/v1/articles?source=' + this.source.id + '&apiKey=5ba4f0af181a460eb1150684b3f96114').then((response) => {
console.log(response);
let data = response.data;
if(data.status === 'ok')
{
this.newsList = data.articles;
}
this.loading = false;
});
}
},
changeLayout(){
if(this.grid){
this.span = 8;
this.offset = 0;
return;
}

this.span = 14;
this.offset = 5;
}
}

}
</script>

<style scoped>
.wrapper {
padding: 5px;
background: #F9FAFC;
max-width: 500;
margin: 10px auto;
border-radius: 4px;
}

.description {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}

a{
text-decoration: none;
}
.time {
font-size: 13px;
color: #999;
}

.bottom {
margin-top: 13px;
line-height: 12px;
}

.button {
padding: 0;
float: right;
}

.image {
width: 100%;
display: block;
}

.clearfix:before,
.clearfix:after {
display: table;
content: "";
}

.clearfix:after {
clear: both
}
</style>

Using two components

In src/App.vue. Change the file to 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
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
<template>
<div id="app" v-loading.fullscreen.lock="loading">
<SourceList></SourceList>
<NewsList></NewsList>
</div>
</template>

<script>
import SourceList from './components/SourceList'
import NewsList from './components/NewsList'

export default {
name: 'app',
created(){
// get news resources when the app created
this.fetchSource();
},
data(){
return{
// the flag used to control the loading bar
loading:false
}
},
components: {
// register two components created above
SourceList,
NewsList,
},
methods:{
fetchSource(){
this.loading = true;
// get news resources
this.axios.get('https://newsapi.org/v1/sources?language=en').then((response) => {
let data = response.data;
// object used to store the resorted data
let hash = {};
// news sources that will be used in the elementui selector
let sources = [];
// if we get the response data
if(data.status === 'ok'){
// resort the data based on their category.
data.sources.forEach(function(e){
let originalCat = e.category;
let category = originalCat.charAt(0).toUpperCase() + originalCat.slice(1);
hash[category] ? hash[category].options.push(e) : (hash[category] = {label:category,options:[e]});
});

// based on the resorted data, populate the array, which could be used in the elementui selector
for(let e in hash){
sources.push(hash[e]);
}

// update sources
this.$store.dispatch('update_sources',sources);

// remove loading bar
this.loading = false;
}
});
}
}
}
</script>

via GIPHY