Browse Source

Add the store and router

Lucas Verney 2 years ago
parent
commit
02cb89ba93

+ 0
- 9
.editorconfig View File

@@ -1,9 +0,0 @@
1
-root = true
2
-
3
-[*]
4
-charset = utf-8
5
-indent_style = space
6
-indent_size = 2
7
-end_of_line = lf
8
-insert_final_newline = true
9
-trim_trailing_whitespace = true

+ 1
- 0
.eslintrc.js View File

@@ -33,6 +33,7 @@ module.exports = {
33 33
     'indent': ['error', 4],
34 34
     'import/prefer-default-export': 'off',
35 35
     'no-console': 'off',
36
+    'no-underscore-dangle': 'off',
36 37
     // Ignore assignment to state
37 38
     'no-param-reassign': [
38 39
         "error",

+ 2
- 1
package.json View File

@@ -21,7 +21,8 @@
21 21
     "vue": "^2.5.2",
22 22
     "vue-i18n": "^7.3.2",
23 23
     "vue-router": "^3.0.1",
24
-    "vuetify": "^0.17.4"
24
+    "vuetify": "^0.17.4",
25
+    "vuex": "^3.0.1"
25 26
   },
26 27
   "devDependencies": {
27 28
     "autoprefixer": "^7.1.2",

+ 1
- 6
src/App.vue View File

@@ -31,7 +31,7 @@
31 31
             <v-container class="panel">
32 32
                 <v-layout row wrap>
33 33
                     <v-flex xs12>
34
-                        <FlatList/>
34
+                        <router-view></router-view>
35 35
                     </v-flex>
36 36
                 </v-layout>
37 37
             </v-container>
@@ -43,12 +43,7 @@
43 43
 </template>
44 44
 
45 45
 <script>
46
-import FlatList from './components/FlatList.vue';
47
-
48 46
 export default {
49
-    components: {
50
-        FlatList,
51
-    },
52 47
     data() {
53 48
         return {
54 49
             drawer: true,

+ 30
- 27
src/api/index.js View File

@@ -5,7 +5,7 @@ import * as CONSTANTS from '@/constants';
5 5
 require('es6-promise').polyfill();
6 6
 require('isomorphic-fetch');
7 7
 
8
-const postProcessAPIResults = (flatFromAPI) => {
8
+function _postProcessAPIResults(flatFromAPI) {
9 9
     /* eslint-disable camelcase */
10 10
     const flat = Object.assign({}, flatFromAPI);
11 11
     // Augment fields values
@@ -39,36 +39,30 @@ const postProcessAPIResults = (flatFromAPI) => {
39 39
 
40 40
     return flat;
41 41
     /* eslint-enable camelcase */
42
-};
42
+}
43 43
 
44
-export const getFlats = (callback) => {
45
-    fetch(`${CONSTANTS.BASE_API_URL}/api/v1/flats`)
44
+function getFlats() {
45
+    return fetch(`${CONSTANTS.BASE_API_URL}/api/v1/flats`)
46 46
         .then(response => response.json())
47
-        .then((json) => {
48
-            const flats = json.data.map(postProcessAPIResults);
49
-            callback(flats);
50
-        })
47
+        .then(json => json.data.map(_postProcessAPIResults))
51 48
         .catch((ex) => {
52 49
             console.error(`Unable to parse flats: ${ex}.`);
53 50
         });
54
-};
51
+}
55 52
 
56
-export const getFlat = (flatId, callback) => {
57
-    fetch(
53
+function getFlat(flatId) {
54
+    return fetch(
58 55
         `${CONSTANTS.BASE_API_URL}/api/v1/flats/${encodeURIComponent(flatId)}`,
59 56
     )
60 57
         .then(response => response.json())
61
-        .then((json) => {
62
-            const flat = postProcessAPIResults(json.data);
63
-            callback(flat);
64
-        })
58
+        .then(json => _postProcessAPIResults(json.data))
65 59
         .catch((ex) => {
66 60
             console.error(`Unable to parse flats: ${ex}.`);
67 61
         });
68
-};
62
+}
69 63
 
70
-export const updateFlat = (flatId, updatedFields, callback) => {
71
-    fetch(
64
+function updateFlat(flatId, updatedFields) {
65
+    return fetch(
72 66
         `${CONSTANTS.BASE_API_URL}/api/v1/flats/${encodeURIComponent(flatId)}`,
73 67
         {
74 68
             method: 'PATCH',
@@ -78,25 +72,26 @@ export const updateFlat = (flatId, updatedFields, callback) => {
78 72
             body: JSON.stringify(updatedFields),
79 73
         },
80 74
     )
81
-        .then(callback)
75
+        .then(response => response.json())
76
+        .then(json => _postProcessAPIResults(json.data))
82 77
         .catch((ex) => {
83 78
             console.error(`Unable to update flat status: ${ex}.`);
84 79
         });
85
-};
80
+}
86 81
 
87
-export const getTimeToPlaces = (callback) => {
88
-    fetch(
82
+function getTimeToPlaces() {
83
+    return fetch(
89 84
         `${CONSTANTS.BASE_API_URL}/api/v1/time_to_places`,
90 85
     )
91 86
         .then(response => response.json())
92
-        .then(json => callback(json.data))
87
+        .then(json => json.data)
93 88
         .catch((ex) => {
94 89
             console.error(`Unable to fetch time to places: ${ex}.`);
95 90
         });
96
-};
91
+}
97 92
 
98
-export const doSearch = (query, callback) => {
99
-    fetch(
93
+function doSearch(query) {
94
+    return fetch(
100 95
         '/api/v1/search',
101 96
         {
102 97
             method: 'POST',
@@ -109,8 +104,16 @@ export const doSearch = (query, callback) => {
109 104
         },
110 105
     )
111 106
         .then(response => response.json())
112
-        .then(json => callback(json.data))
107
+        .then(json => json.data.map(_postProcessAPIResults))
113 108
         .catch((ex) => {
114 109
             console.error(`Unable to perform search: ${ex}.`);
115 110
         });
111
+}
112
+
113
+export {
114
+    getFlats,
115
+    getFlat,
116
+    updateFlat,
117
+    getTimeToPlaces,
118
+    doSearch,
116 119
 };

+ 36
- 0
src/components/FlatDetails.vue View File

@@ -0,0 +1,36 @@
1
+<template>
2
+    <div v-if="!isLoading">
3
+        <p>{{ flat.id }}</p>
4
+    </div>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+    data() {
10
+        return {
11
+            isLoading: false,
12
+        };
13
+    },
14
+    created() {
15
+        this.fetchData();
16
+    },
17
+    watch: {
18
+        // call again the method if the route changes
19
+        $route: 'fetchData',
20
+    },
21
+    methods: {
22
+        fetchData() {
23
+            this.isLoading = true;
24
+
25
+            this.$store.dispatch('getFlat', this.$route.params.flatId).then(() => {
26
+                this.isLoading = false;
27
+            });
28
+        },
29
+    },
30
+    computed: {
31
+        flat() {
32
+            return this.$store.getters.flat(this.$route.params.flatId);
33
+        },
34
+    },
35
+};
36
+</script>

+ 6
- 2
src/components/FlatEntry.vue View File

@@ -2,7 +2,7 @@
2 2
     <v-card class="flat-entry">
3 3
         <v-card-text>
4 4
             <v-container grid-list-sm>
5
-                <v-layout row align-center>
5
+                <v-layout row align-center v-on:click.stop="goToFlatDetailsPage">
6 6
                     <v-flex xs4 text-xs-center md2>
7 7
                         <v-layout column>
8 8
                             <v-flex d-flex xs12>
@@ -44,7 +44,7 @@
44 44
                         </v-btn>
45 45
                     </v-flex>
46 46
                     <v-flex xs1 hidden-sm-and-down>
47
-                        <a :href="urls[0]" target="_blank">
47
+                        <a :href="urls[0]" target="_blank" v-on:click.stop="">
48 48
                             <v-btn flat icon>
49 49
                                 <v-icon class="fa-icon">fa-external-link</v-icon>
50 50
                             </v-btn>
@@ -66,6 +66,7 @@ export default {
66 66
         Avatar,
67 67
     },
68 68
     props: {
69
+        id: String,
69 70
         title: String,
70 71
         thumbnail: String,
71 72
         area: Number,
@@ -88,6 +89,9 @@ export default {
88 89
         },
89 90
     },
90 91
     methods: {
92
+        goToFlatDetailsPage() {
93
+            this.$router.push({ name: 'FlatDetails', params: { flatId: this.id } });
94
+        },
91 95
         deleteFlat() {
92 96
             // TODO
93 97
             console.log('Delete');

+ 21
- 6
src/components/FlatList.vue View File

@@ -1,5 +1,5 @@
1 1
 <template>
2
-    <div>
2
+    <div v-if="!isLoading">
3 3
         <template v-for="flat in flats">
4 4
             <FlatEntry v-bind="flat"></FlatEntry>
5 5
         </template>
@@ -7,7 +7,6 @@
7 7
 </template>
8 8
 
9 9
 <script>
10
-import * as api from '../api';
11 10
 import FlatEntry from './FlatEntry.vue';
12 11
 
13 12
 export default {
@@ -15,14 +14,30 @@ export default {
15 14
         FlatEntry,
16 15
     },
17 16
     data() {
17
+        // TODO: Error handling
18 18
         return {
19
-            flats: [],
19
+            isLoading: false,
20
+            sortKey: 'cost',
20 21
         };
21 22
     },
22 23
     created() {
23
-        api.getFlats((flats) => {
24
-            this.flats = flats;
25
-        });
24
+        this.fetchData();
25
+    },
26
+    watch: {
27
+        // call again the method if the route changes
28
+        $route: 'fetchData',
29
+    },
30
+    methods: {
31
+        fetchData() {
32
+            this.isLoading = true;
33
+
34
+            this.$store.dispatch('getFlats').then(() => { this.isLoading = false; });
35
+        },
36
+    },
37
+    computed: {
38
+        flats() {
39
+            return this.$store.getters.sortedFlats(this.sortKey);
40
+        },
26 41
     },
27 42
 };
28 43
 </script>

+ 4
- 1
src/components/ui/Avatar.vue View File

@@ -8,7 +8,10 @@
8 8
 <script>
9 9
 export default {
10 10
     props: {
11
-        thumbnail: String,
11
+        thumbnail: {
12
+            type: String,
13
+            default: '',
14
+        },
12 15
         alt: {
13 16
             type: String,
14 17
             default: 'Thumbnail',

+ 8
- 0
src/main.js View File

@@ -1,6 +1,8 @@
1 1
 import Vue from 'vue';
2 2
 import VueI18n from 'vue-i18n';
3
+import VueRouter from 'vue-router';
3 4
 import Vuetify from 'vuetify';
5
+import Vuex from 'vuex';
4 6
 
5 7
 import 'font-awesome/css/font-awesome.css';
6 8
 import 'material-design-icons/iconfont/material-icons.css';
@@ -8,12 +10,18 @@ import 'vuetify/dist/vuetify.css';
8 10
 
9 11
 import App from './App.vue';
10 12
 import i18n from './i18n';
13
+import router from './router';
14
+import store from './store';
11 15
 
12 16
 Vue.use(VueI18n);
17
+Vue.use(VueRouter);
13 18
 Vue.use(Vuetify);
19
+Vue.use(Vuex);
14 20
 
15 21
 const app = new Vue({  // eslint-disable-line no-unused-vars
16 22
     el: '#app',
17 23
     render: h => h(App),
18 24
     i18n: new VueI18n(i18n),
25
+    router: new VueRouter(router),
26
+    store: new Vuex.Store(store),
19 27
 });

+ 13
- 11
src/router/index.js View File

@@ -1,15 +1,17 @@
1
-import Vue from 'vue'
2
-import Router from 'vue-router'
3
-import HelloWorld from '@/components/HelloWorld'
1
+import FlatList from '@/components/FlatList.vue';
2
+import FlatDetails from '@/components/FlatDetails.vue';
4 3
 
5
-Vue.use(Router)
6
-
7
-export default new Router({
4
+export default ({
8 5
     routes: [
9 6
         {
10 7
             path: '/',
11
-            name: 'HelloWorld',
12
-            component: HelloWorld
13
-        }
14
-    ]
15
-})
8
+            name: 'FlatList',
9
+            component: FlatList,
10
+        },
11
+        {
12
+            path: '/flat/:flatId',
13
+            name: 'FlatDetails',
14
+            component: FlatDetails,
15
+        },
16
+    ],
17
+});

+ 13
- 0
src/store/actions.js View File

@@ -0,0 +1,13 @@
1
+import * as api from '@/api';
2
+import * as types from './mutations-types';
3
+
4
+export default {
5
+    getFlats({ commit }) {
6
+        return api.getFlats().then(flats => commit(types.STORE_FLATS, { flats }));
7
+    },
8
+    getFlat({ commit }, { flatId }) {
9
+        return api.getFlat(flatId).then(
10
+            flat => commit(types.MERGE_FLATS, { flats: [flat] }),
11
+        );
12
+    },
13
+};

+ 6
- 0
src/store/getters.js View File

@@ -0,0 +1,6 @@
1
+export default {
2
+    sortedFlats: state => sortKey => state.flats.sort(
3
+        (flat1, flat2) => flat1[sortKey] > flat2[sortKey],
4
+    ),
5
+    flat: state => id => state.flats.find(flat => flat.id === id) || {},
6
+};

+ 10
- 0
src/store/index.js View File

@@ -0,0 +1,10 @@
1
+import actions from './actions';
2
+import getters from './getters';
3
+import { initialState as state, mutations } from './mutations';
4
+
5
+export default {
6
+    state,
7
+    actions,
8
+    getters,
9
+    mutations,
10
+};

+ 2
- 0
src/store/mutations-types.js View File

@@ -0,0 +1,2 @@
1
+export const STORE_FLATS = 'STORE_FLATS';
2
+export const MERGE_FLATS = 'MERGE_FLATS';

+ 25
- 0
src/store/mutations.js View File

@@ -0,0 +1,25 @@
1
+import Vue from 'vue';
2
+import * as types from './mutations-types';
3
+
4
+export const initialState = {
5
+    flats: [],
6
+};
7
+
8
+export const mutations = {
9
+    [types.STORE_FLATS](state, { flats }) {
10
+        state.flats = flats;
11
+    },
12
+    [types.MERGE_FLATS](state, { flats }) {
13
+        flats.forEach((flat) => {
14
+            const flatIndex = state.flats.findIndex(
15
+                storedFlat => storedFlat.id === flat.id,
16
+            );
17
+
18
+            if (flatIndex > -1) {
19
+                Vue.set(state.flats, flatIndex, flat);
20
+            } else {
21
+                state.flats.push(flat);
22
+            }
23
+        });
24
+    },
25
+};