Vue JS 2 Tutorial part 3 Input Binding (Creating a blog, part 1) 使用 v-modle 把 input 輸入的內容填入下方的 Preview 區域(不使用即時輸入使用 lazy )
App.vue
引入 addBlog 註冊components’ 使用 add-blog template 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div> <add-blog></add-blog> </div> </template> <script> import addBlog from './components/addBlog.vue' export default { components: { 'add-blog':addBlog }, data () { return { } }, methods: { } } </script>
addBlog.vue
在 data 加入 blog 物件並使用加上屬性 使用 v-model 來操作 input 區域並填入相應的物件屬性 在 preview 區域加入大括號做連結並填入相應的物件屬性 使用 lazy 讓文字不會即時輸入,必須讓 input 取消 focus 才會出現文字在 preview 區域 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <div id="add-blog"> <h2>Add a new Blog Post</h2> <form> <label>Blog Title</label> <input type="text" v-model.lazy="blog.title" required/> <label>Blog Content</label> <textarea v-model.lazy="blog.content"></textarea> </form> <div id="preview"> <h3>Preview Blog</h3> <p>Blog Title:{{blog.title}}</p> <p>Blog Content:</p> <p>{{blog.content}}</p> </div> </div> </template> <script> export default { components: { }, data () { return { blog:{ title: "", content: "" } } }, methods: { } } </script>
本篇 css 取用
Checkbox Binding 延續上一篇的表單,這次要製作的是可以即時更新的checkkboxes
App.vue
首先加入 checkboxes 的 input type 改成 checkbox 在 blog 內加入 categories 選項並用 array 裝 input 區域的 v-model 綁定 blog.categories input 區域的 value 處填入想要在下方 preview 呈現的字樣 在 preview 處新增 ul 並且使用 v-for 印出無序列表內部填入大括號 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <template> <div id="add-blog"> <h2>Add a new Blog Post</h2> <form> <label>Blog Title</label> <input type="text" v-model.lazy="blog.title" required/> <label>Blog Content</label> <textarea v-model.lazy="blog.content"></textarea> <div id="checkboxes"> <label>Ninjas</label> <input type="checkbox" value="ninjas" v-model="blog.categories"/> <label>Wizards</label> <input type="checkbox" value="wizards" v-model="blog.categories"/> <label>Mario</label> <input type="checkbox" value="mario" v-model="blog.categories"/> <label>Cheese</label> <input type="checkbox" value="cheese" v-model="blog.categories"/> </div> </form> <div id="preview"> <h3>Preview Blog</h3> <p>Blog Title:{{blog.title}}</p> <p>Blog Content:</p> <p>{{blog.content}}</p> <p>Blog Categories:</p> <ul> <li v-for="category in blog.categories">{{category}}</li> </ul> </div> </div> </template> <script> export default { components: { }, data () { return { blog:{ title: "", content: "", categories:[] } } }, methods: { } } </script>
Select Box Binding 這次要即時更新下拉式選單到下方的Author
這邊的重點在於:
select 使用 v-model 抓取option option 使用 v-for 印出authors陣列的內容進下拉式選單
App.vue
設置下拉式選單select, option 於 data 處設置 blog.author 屬性 select 處使用 v-model 到 blog.author 待會讓內容動態呈現到 preview 區域 option 處使用 v-for 並在 data 設置 authors 陣列,製作下拉式選單的內容 於 preview 區域做出展示區域並用大括號放入 blog.author 1 2 3 4 <label>Author:</label> <select v-model="blog.author"> <option v-for="author in authors">{{author}}</option> </select>
1 2 3 4 5 6 7 8 9 10 11 <div id="preview"> <h3>Preview Blog</h3> <p>Blog Title:{{blog.title}}</p> <p>Blog Content:</p> <p>{{blog.content}}</p> <p>Blog Categories:</p> <ul> <li v-for="category in blog.categories">{{category}}</li> </ul> <p>Author: {{blog.author}}</p> </div>
HTTP Requests - POST 本篇會介紹如何在 Vue cli 內使用 Http requests
首先需要先下載vue-resource npm install vue-resource
並且在json檔案確認是否安裝成功 下一步會示範如何操作 vue-resource 引入 VueResource 進 main.js 使用 main.js
1 2 3 4 5 6 7 8 9 10 import Vue from 'vue' import App from './App.vue' import VueResource from 'vue-resource' Vue.use(VueResource); new Vue({ el: '#app', render: h => h(App) })
後綴修飾符.prevent可防止瀏覽器預設行為
1 <button v-on:click.prevent="post">Add Blog</button>
使用 $http.post後方加入要串接的後端位置(範例處使用 {JSON} Placeholder 模擬) 抓取 blog.title, blog.content 設置 userId 為 1 (只是做測試可以隨意設置) 使用 .then 抓取 response 並且印出 1 2 3 4 5 6 7 8 9 10 11 methods: { post:function (){ this.$http.post('https://jsonplaceholder.typicode.com/posts',{ title: this.blog.title, body: this.blog.content, userId:1 }).then(function(data){ console.log(data); }); } }
印出結果:
如果成功印出 response 就代表引入的 vue-resource 成功運作!
針對 post 成功後的畫面動態 show 出成功字樣(v-if) form表格的部分消失(v-if) 預設狀態是 false,因為只要當點 post 按鈕後才會觸發
1 2 3 4 5 6 7 8 9 10 11 12 data () { return { blog:{ title: "", content: "", categories:[], author: "", }, authors:["The Net Ninja","The Aveger", "The Vue vindicator"], submitted: false, } },
加入this.submitted 為 true 就可以再點擊後觸發其屬性為 true,讓 v-if 的條件觸發
1 2 3 4 5 6 7 8 9 10 11 12 methods: { post:function (){ this.$http.post('https://jsonplaceholder.typicode.com/posts',{ title: this.blog.title, body: this.blog.content, userId:1 }).then(function(data){ console.log(data); this.submitted = true; }); } }
使用 v-if="!submitted"
代表點擊後會變成false,未點擊前則是true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <form v-if="!submitted"> <label>Blog Title</label> <input type="text" v-model.lazy="blog.title" required/> <label>Blog Content</label> <textarea v-model.lazy="blog.content"></textarea> <div id="checkboxes"> <label>Ninjas</label> <input type="checkbox" value="ninjas" v-model="blog.categories"/> <label>Wizards</label> <input type="checkbox" value="wizards" v-model="blog.categories"/> <label>Mario</label> <input type="checkbox" value="mario" v-model="blog.categories"/> <label>Cheese</label> <input type="checkbox" value="cheese" v-model="blog.categories"/> </div> <label>Author:</label> <select v-model="blog.author"> <option v-for="author in authors">{{author}}</option> </select> <button v-on:click.prevent="post">Add Blog</button> </form>
show出成功字樣 form 消失
HTTP Requests - GET 本篇會使用 GET 方法來取得文章填入網頁
引入新的分頁 showBLogs,並且註冊components,下一步使用在template上 App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div> <show-blogs></show-blogs> </div> </template> <script> import addBlog from './components/addBlog.vue' import showBlogs from './components/showBlogs.vue' export default { components: { 'add-blog':addBlog, 'show-blogs':showBlogs }, data () { return { } }, methods: { } } </script>
使用 created hook 這個生命階段來使用 GET 方法 取得假文 觀察 body 會發現這回傳的文章有100篇故使用 slice 方法取得10篇 把得到的 response 存到 data 內的 blogs:[] 使用 v-for 把 data 內的資料印出來並填入 h2, article 中 showBlogs.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <template> <div id="show-blogs"> <h1>All Blog Articles</h1> <div v-for="blog in blogs" class="single-blog"> <h2>{{blog.title}}</h2> <article>{{blog.body}}</article> </div> </div> </template> <script> export default { data () { return { blogs: [] } }, methods: { }, created(){ this.$http.get('https://jsonplaceholder.typicode.com/posts').then(function(data){ console.log(data); this.blogs = data.body.slice(0,10); }) } } </script>
印出結果:
Custom Directives 製作客製化的 Directives (如 v-rainbow, v-theme 等等)
前往 main.js 使用,這樣一來所有的components都可以使用客製化的Directives
用法:
1 2 3 Vue.directive('客製化的Directive的輸入名字',{後方輸入要使用的lifecycle hook(el:代表選取的元素, binding: 代表directive後方的內容, vnode 目前用不到){ 這邊輸入要操作的內容 }})
v-rainbow: 這邊做的事情是選取元素的顏色並且使用隨機 v-theme: 使用判斷式,如果輸入參數wide, narrow 會讓Blog的大小改變 .arg
代表 v-theme:column 後方 column 的部分,並對其設定背景色以及 paddingmain.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //Custom directives Vue.directive('rainbow', { bind(el, binding, vnode) { el.style.color = `#${Math.random().toString().slice(2,8)}` } }) Vue.directive('theme', { bind(el, binding, vnode) { if (binding.value === 'wide') { el.style.maxWidth = '1200px'; } else if (binding.value === 'narrow') { el.style.maxWidth = "560px" } if (binding.arg == 'column') { el.style.background = '#ddd'; el.style.padding = '20px'; } } })
showBlogs.vue
針對整個Blog區域做客製化 v-theme:column=”narrow” 參數: column 的部分做出包住整個 div 背景色以及padding binding value: 則是決定其max-width的大小 narrow: 560px, wide: 1200px
針對Blog內部的h2做客製化 v-rainbow 把標題文字色彩作隨機呈現
1 2 3 4 5 6 7 8 9 <template> <div v-theme:column="'narrow'"id="show-blogs"> <h1>All Blog Articles</h1> <div v-for="blog in blogs" class="single-blog"> <h2 v-rainbow>{{blog.title}}</h2> <article>{{blog.body}}</article> </div> </div> </template>
印出結果:
Filters Filter 只會改變呈現在 template 的區域而不會改變 data 區域的資料
使用 Filter 把所有 Blog 文章 title 改成大寫 使用 Filter 把所有的內容長度不會超過100個字 main.js
使用方式跟使用components以及客製化directive都很像
最前面的字串填入 filter 名稱 function 參數則是要被操作的內容 to-uppercase 的部分處理內文並使用方法 toUpperCase()
snippet 則是內文做 slice(0,100) +’…’ 的方式來取出前100個字母以及後方加入…字樣代表內容沒有顯示完全 1 2 3 4 5 6 7 8 Vue.filter('to-uppercase' , function (value ) { return value.toUpperCase(); }) Vue.filter('snippet' , function (value ) { return value.slice(0 , 100 ) + '...' ; })
showBlogs.vue
主要filter會操作在tag的部分對動態的內容作filter的動作
撰寫方式是在動態內容的右側加入 “|” filter 名稱即可,不需要操作到 data
{{blog.title | to-uppercase}}
1 2 3 4 5 6 7 8 9 <template> <div v-theme:column="'narrow'"id="show-blogs"> <h1>All Blog Articles</h1> <div v-for="blog in blogs" class="single-blog"> <h2 v-rainbow>{{blog.title | to-uppercase}}</h2> <article>{{blog.body | snippet}}</article> </div> </div> </template>
印出結果:
title 轉成大寫 內文最多100個字母並且在末端加上”…”
Custom Search Filter 製作一個 filter 功能的 input 可以篩選 blog 的 title 以及文章的內容
App.vue
首先做一個 input 區域出來 為了取得打進去 input 的值使用 v-model data 處新增 input 的內容 使用 computed 設置 function filteredBlogs 返回 this.blogs.filter(blog) 使用filter 在 blogs 並且返回 match 是 true的部分 並把 filteredBlogs 引入 v-for 內只呈現篩選過後的內容1 2 3 4 5 6 7 8 9 10 <template> <div v-theme:column="'narrow'"id="show-blogs"> <h1>All Blog Articles</h1> <input type="text" v-model="search" placeholder="search blogs"> <div v-for="blog in filteredBlogs" class="single-blog"> <h2 v-rainbow>{{blog.title | to-uppercase}}</h2> <article>{{blog.body | snippet}}</article> </div> </div> </template>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 data () { return { blogs: [], search:'' } }, methods: { }, created(){ this.$http.get('https://jsonplaceholder.typicode.com/posts').then(function(data){ console.log(data); this.blogs = data.body.slice(0,10); }) }, computed: { filteredBlogs:function (){ return this.blogs.filter((blog)=>{ return blog.title.match(this.search); }); } }
印出結果:
只會印出經過篩選的內容( title )
Registering Things Locally 這部分要示範如何註冊 directive, filter 在 local 而不需要註冊在 global
直接用這樣的方式使用在 local 資料夾內即可,就不需要註冊在全域的 main.js 瞜
1 2 3 4 5 filters:{ toUppercase(value){ return value.toUpperCase(); } }
1 2 3 4 5 6 7 directives:{ 'rainbow':{ bind(el, binding, vnode) { el.style.color = `#${Math.random().toString().slice(2,8)}` } } }
Mixins 在 Sass 中也有出現,基本上就是一段程式碼可以重複利用在不同的地方
把重複利用的程式碼抽取出來擺到 mixins 裡面 新建一個 mixins 資料夾
export 要使用的元件 mixins.js
1 2 3 4 5 6 7 8 9 export default { computed: { filteredBlogs: function () { return this.blogs.filter((blog) => { return blog.title.match(this.search); }); } } }
在要使用的本地端引入 1 import mixin from '../mixins/mixins'
在要使用的本地端註冊 屬性名稱 + 陣列內部填入使用元件的名稱(也就是 import 進來那個名字)
這樣就可以在各個檔案使用元件瞜! Setting up Routing 藉由設置 Routing 就可以透過輸入網址的方式前往不同的分頁
使用 npm i vue-router
下載 在 main.js 引入檔案,並且先設置一個變數待會使用 main.js
1 2 3 4 5 import VueRouter from 'vue-router' const router = new VueRouter({ });
設置 routes.js 檔案 引入要使用的分頁 設定 path, component 1 2 3 4 5 6 7 8 9 10 11 12 13 import showBlogs from './components/showBlogs' import addBlog from './components/addBlog' export default [{ path: '/', component: showBlogs }, { path: '/add', component: addBlog } ]
回到 main.js 引入剛剛建立好的 routes.js 並給剛剛建立好的變數輸入屬性 routes: Routes 並且在 Vue 實體處引入 router 屬性內容為 Router 1 2 3 4 5 6 7 8 9 10 11 12 import Routes from './routes' const router = new VueRouter({ routes: Routes }); new Vue({ el: '#app', render: h => h(App), router: router })
就可以做到使用網址切換分頁的動作瞜!
Hash vs History (Routing) #
在這邊做到的事情不會對 serve 發送 request,對於 SEO 有不好的影響,預設模式History
則會對 serve 發送 request一般推薦使用 History
當使用 History
時 url 看起來比較正常 http://localhost:8080/add ,但是使用者直接操作這個網址是會得到404的!
1 2 3 4 const router = new VueRouter({ routes: Routes, mode: 'history' });
參考資源:
Kuro Vue 008
Adding Router Links 使用 router-link 來製作可以跳轉頁面的 navbar
創建新 component header.vue 使用router-link 並且加上屬性 to=”路徑” 使用 exact 確保路徑必須完全一致才會啟動 router-link-active 這個 class,讓 active特效正常運作
1 2 3 4 5 6 7 8 <template> <nav> <ul> <li><router-link to="/" exact>Blog</router-link></li> <li><router-link to="/add" exact>Add a new blog</router-link></li> </ul> </nav> </template>
至 App.vue 引入並且註冊使用 header.vue,並且在template中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div> <app-header></app-header> <router-view></router-view> </div> </template> <script> import addBlog from './components/addBlog.vue' import showBlogs from './components/showBlogs.vue' import showTitle from './components/showTitle.vue' import header from './components/header.vue' export default { components: { 'add-blog':addBlog, 'show-blogs':showBlogs, 'show-title':showTitle, 'app-header': header },
header CSS
Route Parameters 這邊主要操作 $route.params.id
來取得每一篇 Blog 的參數並且藉由著個參數來個別顯示 Blog文章
routes.js 內設置新分頁 這邊 path 設置 :id (id 這個名稱可以自訂) 是為了讓 params 可以抓取到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import singleBlog from './components/singleBlog' export default [{ path: '/' , component: showBlogs }, { path: '/add' , component: addBlog }, { path: '/blog/:id' , component: singleBlog } ]
設置新分頁 singBlog.vue 在 data 部分使用 $route.params.id 取得參數內容給 id(在 routes.js 內設置) 使用 created 擷取 http get 方法來獲得 Blog 單篇的內容並指派給 blog 並且把 blog 推到 template 內 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div id="single-blog"> <h1>{{blog.title}}</h1> <article>{{blog.body}}</article> </div> </template> <script> export default { data() { return { id:this.$route.params.id, blog:{} } }, created () { this.$http.get('http://jsonplaceholder.typicode.com/posts/' + this.id) .then(function(data){ console.log(data); this.blog = data.body; }) } } </script>
到 showBlogs.vue 頁面處理頁面的 title 並修改成 router-link 使用 v-bind 屬性 to 並且連結處 使用 “‘/blog/‘ + blog.id” 的方式來取得點擊每篇 blog 取得的 id 並藉此跳轉到對應的 blog 內容
1 2 3 4 5 6 7 8 9 10 <template> <div v-theme:column="'narrow'"id="show-blogs"> <h1>All Blog Articles</h1> <input type="text" v-model="search" placeholder="search blogs"> <div v-for="blog in filteredBlogs" class="single-blog"> <router-link v-bind:to="'/blog/' + blog.id"><h2>{{blog.title | to-uppercase}}</h2></router-link> <article>{{blog.body | snippet}}</article> </div> </div> </template>
得出結果:
每一篇的 blog 都會有這些屬性在裡面,所以當我們點擊某一篇 blog 的 title 時,他內部的 router-link 就會接收到他的 blog.id ,並且跳轉到 /blog/ blog.id 的頁面
實作結果:
Posting to Firebase 使用 firebase 把 blog 內容 POST 上去
到 firebase 官網註冊後,點選 Realtime Database 修改誠如下方讀寫都為 true 並且發布
回去資料頁面複製 url 待會做使用
到 addBlog.vue 修改 post 填入剛剛申請的 firebase url ,並請後面填入格式 posts.json 於 url 後面填入要 post 的主體 也就是 this.blog
1 2 3 4 5 6 7 post:function (){ this.$http.post('https://vue-project-dc556-default-rtdb.firebaseio.com/posts.json', this.blog).then(function(data){ console.log(data); this.submitted = true; }); } }
測試 POST 功能
成功推上 firebase 後會出現這個 response,並且內部有個專屬的 name 可以辨別這是哪一篇 POST
從官網的 Realtime Database 中可以看到剛剛上傳那篇 blog
Retrieving Posts from Firebase 剛剛我們順利的把 blog 推上 firebase ,現在要把這些位於資料庫中的資料拿來使用摟!
處理 firebase 取得的資料 首先我們要修改之前使用 jsonplaceholder 的部分 url 改成 firebase 這邊的 url 會產生兩個 .then(data):
第一個:data 會產生結果如下
裡面有四個很重要的屬性 author, categories, content, title ,但是我們需要的只有 body 的部分因此我們回傳 data.json();
但是他是個 promise 因此迎來第二個 .then(data) 來做處理(下圖可以發現沒有 id 屬性)
第二個: data 這次等候 promise 回傳後,得到所有 firebase 上面的貼文物件
但是因為我們只要貼文內部的 body 部分所以
首先創立一個空陣列 blogsArray 使用 for … in 印出所有的 id(也是這邊物件裡面的 key ) 給 data 新增屬性 id data[key].id = key
如此一來就可以透過 id 辨別 blog push 到空陣列中並把已經充滿貼文的 blogsArray 指派給 this.blogs 就可以在 template 中使用 blogsArray 的內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 created(){ this.$http.get('https://vue-project-dc556-default-rtdb.firebaseio.com/posts.json').then(function(data){ return data.json(); }).then(function(data){ var blogsArray = []; for(let key in data){ // console.log(data[key]); data[key].id = key blogsArray.push(data[key]); } // console.log(blogsArray); this.blogs = blogsArray; }) }
針對 showBlogs.vue 的呈現 這邊把 blog 的屬性使用到
router-link 內部 讓其可以讀取到每篇 blog 的 id 以及 v-for 要印出的 title, content 1 2 3 4 5 6 7 8 9 10 <template> <div v-theme:column="'narrow'"id="show-blogs"> <h1>All Blog Articles</h1> <input type="text" v-model="search" placeholder="search blogs"> <div v-for="blog in filteredBlogs" class="single-blog"> <router-link v-bind:to="'/blog/' + blog.id"><h2>{{blog.title | to-uppercase}}</h2></router-link> <article>{{blog.content | snippet}}</article> </div> </div> </template>
調整 singleBlog.vue 一樣先調整 url ,這邊因為必須有轉檔的因素所以 url 內容需要調整,並且因為取得的 blog 只有一條就不需要迭代直接指派給 this. blog
就好
在 template 處 使用title, content, author, categories 等屬性 來呈現內容
1 2 3 4 5 6 7 8 9 created () { this.$http.get('https://vue-project-dc556-default-rtdb.firebaseio.com/posts/' + this.id+'.json') .then(function(data){ return data.json(); }).then(function(data) { this.blog = data; }) }