You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.5 KiB

Tutorial vue pwa

  • Vue PWA template is built on top of Vue webpack template
  • Webpack is a modern and powerful module bundler for Javascript application
  • Project layout
    • build: webpack and vue-loader configuration files
    • config: app config (environments, parameters)
    • src: application source code
    • statis: images, css & other public assets
    • test: unit test files (Karma & Mocha)
$ npm install -g vue-cli

// use pwa template to scaffold a project
$ vue init pwa cropchat

$ cd cropchat
$ npm install
$ npm run dev

// launch browser at http://localhost:8080

  • Vue pwa template provide default static/manifest.json. Edit it to customize the project
{
  "name": "cropchat",
  "short_name": "cropchat",
  "icons": [
    {
      "src": "/static/img/icons/cropchat-icon-64x64.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/static/img/icons/cropchat-icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "/static/img/icons/cropchat-icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    },
    {
      "src": "/static/img/icons/cropchat-icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/",
  "display": "fullscreen",
  "orientation": "portrait",
  "background_color": "#2196f3",
  "theme_color": "#2196f3"
}
  • index.html already provide the manifest link
...
<meta name="viewport" content="width=device-width, initial-scale=1">

...
<link rel="manifest" href="<%= htmlWebpackPlugin.files.publicPath %>static/manifest.json">


Access from mobile

  • use ngrok to forward internet address to localhost host
$ npm install -g ngrok

$ ngrok http 8080

ngrok by @inconshreveable                                                                   (Ctrl+C to quit)
                                                                                                            
Session Status                online                                                                        
Version                       2.1.18                                                                        
Region                        United States (us)                                                            
Web Interface                 http://127.0.0.1:4040                                                         
Forwarding                    http://5ef29506.ngrok.io -> localhost:8080                                    
Forwarding                    https://5ef29506.ngrok.io -> localhost:8080                                   
                                                                                                            
Connections                   ttl     opn     rt1     rt5     p50     p90                                   
                              39      3       0.01    0.01    120.01  881.89
  • Access https://5ef29506.ngrok.io from mobile

Develop Vue App

  • src/components/HomeView.vue

<template>
  <ul class="list">
  </ul>
</template>

<script>
export default {
}
</script>

<style scoped>
  .list {
    width: 100%;
    padding: 0;
  }
</style>
  • src/components/DetailView.vue
<template>
  <div class="card-image">
  </div>
</template>

<script>
  export default {
  }
</script>

<style scoped>
</style>
  • src/components/PostView.vue
<template>
  <div class="waiting">
    Not yet available
  </div>
</template>

<script>
export default {
}
</script>

<style scoped>
  .waiting {
    padding: 10px;
    color: #555;
  }
</style>
  • Update src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HomeView from '@/components/HomeView'
import DetailView from '@/components/DetailView'
import PostView from '@/components/PostView'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/detail/:id',
      name: 'detail',
      component: DetailView
    },
    {
      path: '/post',
      name: 'post',
      component: PostView
    }
  ]
})
  • Remove Hello.vue

Layout Design

  • install material Design
$ npm install material-design-lite --save
  • Update src/App.vue
<script>
  require('material-design-lite')
  ...
</script>
<style>
  @import url('https://fonts.googleapis.com/icon?family=Material+Icons');
  @import url('https://code.getmdl.io/1.2.1/material.blue-red.min.css');
</style>
  • Add Navigation Bar. Update src/App.vue template
<template>
  <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
    <header class="mdl-layout__header">
      <div class="mdl-layout__header-row">
        <span class="mdl-layout-title">CropChat</span>
      </div>
    </header>
    <div class="mdl-layout__drawer">
      <span class="mdl-layout-title">CropChat</span>
      <nav class="mdl-navigation">
        <router-link class="mdl-navigation__link" to="/" @click.native="hideMenu">Home</router-link>
        <router-link class="mdl-navigation__link" to="/post" @click.native="hideMenu">Post a picture</router-link>
      </nav>
    </div>
    <main class="mdl-layout__content">
      <div class="page-content">
        <router-view></router-view>
      </div>
    </main>
  </div>
</template>
  • Hide burger menu upon click menu link
<script>
...
export default {
  name: 'app',
  methods: {
    hideMenu: function () {
      document.getElementsByClassName('mdl-layout__drawer')[0].classList.remove('is-visible')
      document.getElementsByClassName('mdl-layout__obfuscator')[0].classList.remove('is-visible')
    }
  }
}
</script>
  • Populate views
  • Create src/data.js
export default {
  pictures: [
    {
      'id': 0,
      'url': 'https://25.media.tumblr.com/tumblr_m40h4ksiUa1qbyxr0o1_400.gif',
      'comment': 'A cat game',
      'info': 'Posted by Kevin on Friday'
    },
    {
      'id': 1,
      'url': 'https://25.media.tumblr.com/tumblr_lhd7n9Qec01qgnva2o1_500.jpg',
      'comment': 'Tatoo & cat',
      'info': 'Posted by Charles on Tuesday'
    },
    {
      'id': 2,
      'url': 'https://24.media.tumblr.com/tumblr_m4j2atctRm1qejbiro1_1280.jpg',
      'comment': 'Santa cat',
      'info': 'Posted by Richard on Monday'
    },
    {
      'id': 3,
      'url': 'https://25.media.tumblr.com/tumblr_m3rmbwhVB51qhwmnpo1_1280.jpg',
      'comment': 'Mexico cat',
      'info': 'Posted by Richard on Monday'
    },
    {
      'id': 4,
      'url': 'https://24.media.tumblr.com/tumblr_mceknxs4Lo1qd477zo1_500.jpg',
      'comment': 'Curious cat',
      'info': 'Posted by Richard on Monday'
    }
  ]
}
  • Update src/components/HomeView.vue to display mocked data
<script>
  import data from '../data'
  export default {
    methods: {
      displayDetails (id) {
        this.$router.push({name: 'detail', params: { id: id }})
      }
    },
    data () {
      return {
        'pictures': data.pictures
      }
    }
  }
</script>
  • Update temple ans style
<template>
  <div>
    <div class="mdl-grid">
      <div class="mdl-cell mdl-cell--3-col mdl-cell mdl-cell--1-col-tablet mdl-cell--hide-phone"></div>
      <div class="mdl-cell mdl-cell--6-col mdl-cell--4-col-phone">
        <div v-for="picture in this.pictures" class="image-card" @click="displayDetails(picture.id)">
          <div class="image-card__picture">
            <img :src="picture.url" />
          </div>
          <div class="image-card__comment mdl-card__actions">
            <span>{{ picture.comment }}</span>
          </div>
        </div>
      </div>
    </div>
    <router-link class="add-picture-button mdl-button mdl-js-button mdl-button--fab mdl-button--colored" to="/post">
      <i class="material-icons">add</i>
    </router-link>
  </div>
</template>
...
<style scoped>
  .add-picture-button {
    position: fixed;
    right: 24px;
    bottom: 24px;
    z-index: 998;
  }
  .image-card {
    position: relative;
    margin-bottom: 8px;
  }
  .image-card__picture > img {
    width:100%;
  }
  .image-card__comment {
    position: absolute;
    bottom: 0;
    height: 52px;
    padding: 16px;
    text-align: right;
    background: rgba(0, 0, 0, 0.5);
  }
  .image-card__comment > span {
    color: #fff;
    font-size: 14px;
    font-weight: bold;
  }
</style>