

vue-cli designed to work with projects Vue.js, thanks to him, we, without wasting time on settings, can quickly create a project and get to work.vue-cli following command: npm install -g vue-cli  vue init webpack samplevue cd samplevue npm install  npm run dev  touch ./src/components/Movie.vue touch ./src/components/Reviews.vue  <!-- ./src/components/Movie.vue --> <template> <div class="container">   <div class="row">     <form @submit.prevent="fetchMovie()">       <div class="columns large-8">         <input type="text" v-model="title">       </div>       <div class="columns large-4">         <button type="submit" :disabled="!title" class="button expanded">           Search titles         </button>       </div>     </form>   </div>   <!-- /search form row --> </div> <!-- /container --> </template> fetchMovie() .@submit directive is short for v-on:submit . It is used to listen for DOM events and execute actions or handlers when these events occur. The modifier .prevent helps to create event.preventDefault() in the handler.title we use the v-model directive. And finally, we can bind the attribute of the disabled button so that it will be set to true if the title empty and vice versa. Also note that :disabled β a shortcut for v-bind:disabled . <!-- ./src/components/Movie.vue --> <script> //  URL  API const API_URL = 'https://netflixroulette.net/api/api.php' //    URL        function buildUrl (title) { return `${API_URL}?title=${title}` } export default { name: 'movie', //   data () {   return {     title: '',     error_message: '',     loading: false, // ,         movie: {}   } }, methods: {   fetchMovie () {     let title = this.title     if (!title) {       alert('please enter a title to search for')       return     }     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch((e) => {       console.log(e)     })   } } } </script> data : sets the properties that may be needed in the component.methods : sets the methods of the component. Now there is only one method used to load movie data - fetchMovie() .            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?            ,     : 
 
 <!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7">   <h4> {{ movie.show_title }}</h4>   <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5">   <p>{{ movie.summary }}</p>   <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template> 
  ,   ,       ,    : 
 
 <!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style> 
 βB2:      
   R eview ,        ,      ,        . 
 
   v-for  ,      .     : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <h4 class="uppercase">reviews</h4>   <div class="review" v-for="review in reviews">     <p>{{ review.content }}</p>     <div class="row">       <div class="columns medium-7">         <h5>{{ review.reviewer }}</h5>       </div>       <div class="columns medium-5">         <h5 class="pull-right">{{ review.time }}</h5>       </div>     </div>   </div> </div> </template> <script> const MOCK_REVIEWS = [ {   movie_id: 7128,   content: 'Great show! I loved every single scene. Defintiely a must watch!',   reviewer: 'Jane Doe',   time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () {   return {     mockReviews: MOCK_REVIEWS,     movie: null,     review: {       content: '',       reviewer: ''     }   } }, computed: {   reviews () {     return this.mockReviews.filter(review => {       return review.movie_id === this.movie     })   } } } </script> 
 MOCK_REVIEWS     . ,  ,      . 
 
          : 
 
 <!-- ./src/components/Review.vue --> <template> <div class="container">   <!-- //... -->   <div class="review-form" v-if="movie">     <h5>add new review.</h5>     <form @submit.prevent="addReview">       <label>         Review         <textarea v-model="review.content" cols="30" rows="5"></textarea>       </label>       <label>         Name         <input v-model="review.reviewer" type="text">       </label>       <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>     </form>   </div>   <!-- //... -->   </div> </template> <script> export default { // .. methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       return     }     let review = {       movie_id: this.movie,       content: this.review.content,       reviewer: this.review.reviewer,       time: new Date().toLocaleDateString()     }     this.mockReviews.unshift(review)   } }, //... } </script> 
  , ,     ,  ,   : 
 
 <!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container {   padding: 0 20px; } .review {   border:1px solid #ddd;   font-size: 0.95em;   padding: 10px;   margin: 15px 0 5px 0; } .review h5 {   text-transform: uppercase;   font-weight: bolder;   font-size: 0.7em } .pull-right {   float: right; } .review-form {   margin-top: 30px;   border-top: 1px solid #ddd;   padding: 15px 0 0 0; } </style> 
   movie   Movie  ,     . 
 
 βB3:     
  ,      ,     Vue        .    β  ,      ,        .    : 
 
 touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus 
         fetchMovies() : 
 
 <!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: {   fetchMovie (title) {     this.loading = true     fetch(buildUrl(title))     .then(response => response.json())     .then(data => {       this.loading = false       this.error_message = ''       bus.$emit('new_movie', data.unit) // emit `new_movie` event       if (data.errorcode) {         this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`         return       }       this.movie = data     }).catch(e => { console.log(e) })   } } } 
   created      Review : 
 
 <!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () {   bus.$on('new_movie', movieId => {     this.movie = movieId   }) }, // ... } </script> 
     ,     new_movie ,    movie   movieId ,          . 
 
 ,      ,    App.vue   : 
 
 <!-- ./src/App.vue --> <template> <div id="app">   <div class="container">     <div class="heading">       <h2><font color="#3AC1EF">samplevue.</font></h2>       <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>     </div>     <div class="row">       <div class="columns small-7">         <movie></movie>       </div>       <div class="columns small-5">         <reviews></reviews>       </div>     </div>   </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default {   name: 'app',   components: {     Movie, Reviews   } } </script> <style> #app .heading {   font-family: 'Avenir', Helvetica, Arial, sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale;   text-align: center;   color: #2c3e50;   margin: 60px 0 30px;   border-bottom: 1px solid #eee; } </style> 
               : 
 
 npm run dev 
    ,          API Netflix    . 
 
 C:        Pusher 
    ,       .     ,     ,      . 
 
   ,    post-    ,  pusher ,      . 
 
 βC1:  Pusher 
       Pusher .           . 
 
 βC2:       
      Node.js.  ,   ,  package.json ,    : 
 
 npm install -S express body-parser pusher 
   server.js ,     Express: 
 
 // ./server.js /* *  Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* *  Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* *   post     */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* *   */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)}); 
   Express,   Pusher,     .  YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET  YOUR_CLUSTER     Pusher. 
 
     : /review .      Pusher    review_added   reviews .        .       trigger : 
 
 pusher.trigger(channels, event, data, socketId, callback); 
 βC3:  API- 
    config/index.js ,  ,      API  -,  Vue Webpack.         API . 
 
 // config/index.js module.exports = { // ... dev: {   // ...   proxyTable: {       '/api': {       target: 'http://localhost:5000', //        ,            changeOrigin: true,       pathRewrite: {         '^/api': ''       }     }   },   // ... } } 
   addReview    API  /src/components/Reviews.vue : 
 
 <!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: {   addReview () {     if (!this.movie || !this.review.reviewer || !this.review.content) {       alert('please make sure all fields are not empty')       return     }     let review = {       movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()     }     fetch('/api/review', {       method: 'post',       body: JSON.stringify(review)     }).then(() => {       this.review.content = this.review.reviewer = ''     })   }   // ... }, // ... } </script> 
 βC4:   
    ,   ,  Pusher,        .   pusher-js: 
 
 npm install -S pusher-js 
  Review.vue : 
 
 <!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' //  Pusher export default { // ... created () {   // ...   this.subscribe() }, methods: {   // ...   subscribe () {     let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })     pusher.subscribe('reviews')     pusher.bind('review_added', data => {       this.mockReviews.unshift(data.review)     })   } }, // ... } </script> 
    ,    Pusher   pusher-js .    subscribe ,    : 
 
    reviews    pusher.subscribe('reviews') . 
   review_added   pusher.bind . ,    ,    .              . 
 
 D.    
   server.js  Node-  dev- ,     ,   API    ,   webpack : 
 
 { // ... "scripts": {   "dev": "node server.js & node build/dev-server.js",   "start": "node server.js & node build/dev-server.js",   // ... } } 
    ,    : 
 
 run dev 
  
 Vue.js β     ,          .       ,         Pusher. 
 
  !       -,        ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?, :
<!-- ./src/components/Movie.vue --> <template> <!-- // ... --> <div v-if="loading" class="loader"> <img align="center" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader"> </div> <div v-else-if="error_message"> <h3><font color="#3AC1EF">β{{ error_message }}</font></h3> </div> <div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie"> <div class="columns large-7"> <h4> {{ movie.show_title }}</h4> <img :src="movie.poster" :alt="movie.show_title"> </div> <div class="columns large-5"> <p>{{ movie.summary }}</p> <small><strong>Cast:</strong> {{ movie.show_cast }}</small> </div> </div> </template>
, , , :
<!-- ./src/components/Movie.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #movie { margin: 30px 0; } .loader { text-align: center; } </style>
βB2:
R eview , , , .
v-for , . :
<!-- ./src/components/Review.vue --> <template> <div class="container"> <h4 class="uppercase">reviews</h4> <div class="review" v-for="review in reviews"> <p>{{ review.content }}</p> <div class="row"> <div class="columns medium-7"> <h5>{{ review.reviewer }}</h5> </div> <div class="columns medium-5"> <h5 class="pull-right">{{ review.time }}</h5> </div> </div> </div> </div> </template> <script> const MOCK_REVIEWS = [ { movie_id: 7128, content: 'Great show! I loved every single scene. Defintiely a must watch!', reviewer: 'Jane Doe', time: new Date().toLocaleDateString() } ] export default { name: 'reviews', data () { return { mockReviews: MOCK_REVIEWS, movie: null, review: { content: '', reviewer: '' } } }, computed: { reviews () { return this.mockReviews.filter(review => { return review.movie_id === this.movie }) } } } </script>
MOCK_REVIEWS . , , .
:
<!-- ./src/components/Review.vue --> <template> <div class="container"> <!-- //... --> <div class="review-form" v-if="movie"> <h5>add new review.</h5> <form @submit.prevent="addReview"> <label> Review <textarea v-model="review.content" cols="30" rows="5"></textarea> </label> <label> Name <input v-model="review.reviewer" type="text"> </label> <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button> </form> </div> <!-- //... --> </div> </template> <script> export default { // .. methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } this.mockReviews.unshift(review) } }, //... } </script>
, , , , :
<!-- ./src/components/Review.vue --> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .container { padding: 0 20px; } .review { border:1px solid #ddd; font-size: 0.95em; padding: 10px; margin: 15px 0 5px 0; } .review h5 { text-transform: uppercase; font-weight: bolder; font-size: 0.7em } .pull-right { float: right; } .review-form { margin-top: 30px; border-top: 1px solid #ddd; padding: 15px 0 0 0; } </style>
movie Movie , .
βB3:
, , Vue . β , , . :
touch ./src/bus.js // ./src/bus.js import Vue from 'vue' const bus = new Vue() export default bus
fetchMovies() :
<!-- ./src/components/Movie.vue --> import bus from '../bus' export default { // ... methods: { fetchMovie (title) { this.loading = true fetch(buildUrl(title)) .then(response => response.json()) .then(data => { this.loading = false this.error_message = '' bus.$emit('new_movie', data.unit) // emit `new_movie` event if (data.errorcode) { this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.` return } this.movie = data }).catch(e => { console.log(e) }) } } }
created Review :
<!-- ./src/components/Review.vue --> <script> import bus from '../bus' export default { // ... created () { bus.$on('new_movie', movieId => { this.movie = movieId }) }, // ... } </script>
, new_movie , movie movieId , .
, , App.vue :
<!-- ./src/App.vue --> <template> <div id="app"> <div class="container"> <div class="heading"> <h2><font color="#3AC1EF">samplevue.</font></h2> <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6> </div> <div class="row"> <div class="columns small-7"> <movie></movie> </div> <div class="columns small-5"> <reviews></reviews> </div> </div> </div> </div> </template> <script> import Movie from './components/Movie' import Reviews from './components/Reviews' export default { name: 'app', components: { Movie, Reviews } } </script> <style> #app .heading { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin: 60px 0 30px; border-bottom: 1px solid #eee; } </style>
:
npm run dev
, API Netflix .
C: Pusher
, . , , .
, post- , pusher , .
βC1: Pusher
Pusher . .
βC2:
Node.js. , , package.json , :
npm install -S express body-parser pusher
server.js , Express:
// ./server.js /* * Express */ const express = require('express'); const path = require('path'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname))); /* * Pusher */ const Pusher = require('pusher'); const pusher = new Pusher({ appId:'YOUR_PUSHER_APP_ID', key:'YOUR_PUSHER_APP_KEY', secret:'YOUR_PUSHER_SECRET', cluster:'YOUR_CLUSTER' }); /* * post */ app.post('/review', (req, res) => { pusher.trigger('reviews', 'review_added', {review: req.body}); res.status(200).send(); }); /* * */ const port = 5000; app.listen(port, () => { console.log(`App listening on port ${port}!`)});
Express, Pusher, . YOUR_PUSHER_APP_ID , YOUR_PUSHER_APP_KEY , YOUR_PUSHER_SECRET YOUR_CLUSTER Pusher.
: /review . Pusher review_added reviews . . trigger :
pusher.trigger(channels, event, data, socketId, callback);
βC3: API-
config/index.js , , API -, Vue Webpack. API .
// config/index.js module.exports = { // ... dev: { // ... proxyTable: { '/api': { target: 'http://localhost:5000', // , changeOrigin: true, pathRewrite: { '^/api': '' } } }, // ... } }
addReview API /src/components/Reviews.vue :
<!-- ./src/components/Review.vue --> <script> // ... export default { // ... methods: { addReview () { if (!this.movie || !this.review.reviewer || !this.review.content) { alert('please make sure all fields are not empty') return } let review = { movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString() } fetch('/api/review', { method: 'post', body: JSON.stringify(review) }).then(() => { this.review.content = this.review.reviewer = '' }) } // ... }, // ... } </script>
βC4:
, , Pusher, . pusher-js:
npm install -S pusher-js
Review.vue :
<!-- ./src/components/Review.vue --> <script> import Pusher from 'pusher-js' // Pusher export default { // ... created () { // ... this.subscribe() }, methods: { // ... subscribe () { let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' }) pusher.subscribe('reviews') pusher.bind('review_added', data => { this.mockReviews.unshift(data.review) }) } }, // ... } </script>
, Pusher pusher-js . subscribe , :
reviews pusher.subscribe('reviews') .
review_added pusher.bind . , , . .
D.
server.js Node- dev- , , API , webpack :
{ // ... "scripts": { "dev": "node server.js & node build/dev-server.js", "start": "node server.js & node build/dev-server.js", // ... } }
, :
run dev
Vue.js β , . , Pusher.
! -, ?Source: https://habr.com/ru/post/343374/
All Articles