2017年6月28日 星期三

[學習筆記目錄] 你所不知道的JavaScript: Understanding the Weird Part



這是我在Udemy上修的一門課,當初想增進自己JavaScript的能力,這門課在特價,同時老師的評價也相當的好,所以就想說學學看。這門課和其他一般線上免費的JavaScript教程很不一樣,不只是教程式怎麼寫,而是進一步說明背後的原理和邏輯,學到的很多之前不清楚的概念,特別是在JavaScript中關於繼承、原型還有建構式的地方之前看了很多教學都還是不太瞭解,但這堂課說明的超級清楚,同時又有搭配英文字幕,歡迎大家參考,也很推薦大家上這門課!

JavaScript基本觀念:認識不同的資料型別、運算子和重要概念


JavaScript物件建立:瞭解基本物件建立的方法


JavaScript函式:為什麼說函式也是物件的一種呢?


JavaScript函式進階:瞭解什麼是IIFEs、什麼是closures?


JavaScript原型、繼承和建構式:這是我上過最清楚的說明


親手打造屬於你的 Framework/Library


其他資源


Share:

[學習筆記目錄] JS30 系列文章


JS30 學習筆記連結目錄

另外在 Facebook 社團 線上 Node 讀書會 中有由 @Yujin Chne 主辦的線上讀書會,由 @技安 協助提供社群和直播軟體與錄影,如果想要獲得更多相關資訊的話,歡迎前往該讀書會。這裡也附上由@Yujin Chne整理好的影片連結
我覺得 JS30 真的是非常好的 JS 練習,關於 JS30 的說明可以參考 這一篇(JS30 系列 Day0 - 課程說明) ,下面列的是個人的學習筆記,如果有錯誤的部分歡迎告知:
Share:

2017年6月20日 星期二

[JS小細節] Node Element 在 appendChild 後消失(disappear)!?

為什麼無法正確 appendChild !?

情況描述:Node.appendChild 的使用

Node.appendChild() 是我們在 JavsScripy 中操作 DOM 的時候經常會使用到的方法,特別是在我們使用 JS 建立一個 DOM Element 之後。
舉例來說,假設現在我們的 HTML 結構長這樣:
<div class="demo-1">
  <div class="block block-1"></div>
  <div class="block block-2"></div>
  <div class="block block-3"></div>
</div>
這時候的畫面長這樣子:
假設我想要在每一個 .block 中都添加一個 .innerdiv 時,我們直覺上可能會這樣做:
// STEP1: 利用 document.createElement 建立 DOM Element
let innerElement = document.createElement('div')
innerElement.classList.add('inner')

// STEP2: 選擇每一個 .blocks 並且 appendChild 上去
const blocks = document.querySelectorAll('.block')
blocks.forEach(block => {
    block.appendChild(innerElement)
})
但這時候卻不會出現你預想的畫面,而是只有最後一個 .block 有添加到 .inner 這個 div,畫面會像這樣:
可是我們想要的畫面應該要是這樣:
到底為什麼會這樣呢?
你可能會猜想是 Array.prototype.forEach 的問題,於是我們試著一個一個 appendChild 上去:
// STEP1: 利用 document.createElement 建立 DOM Element
let innerElement = document.createElement('div')
innerElement.classList.add('inner')

// STEP2: 分別選擇各個 block
const block1 = document.querySelector('.block-1')
const block2 = document.querySelector('.block-2')
const block3 = document.querySelector('.block-3')

// STEP3-1: 先 appendChild 到 block1 上
block1.appendChild(innerElement)
看起來好像沒有太大的問題,如我們所料的,appendChild 到 .block1 這個 div 上了:
接著我們來對 .block2 做 appendChild()
// STEP3-2: appendChild 到 block2 上
block1.appendChild(innerElement)
block2.appendChild(innerElement)
不得了了,.block2 有加上 innerElement 了,但是 .block1 的 innerElement 卻不見了:
不死心的,我們在把 .block3 appendChild():
// STEP3-3: appendChild 到 block3 上
block1.appendChild(innerElement)
block2.appendChild(innerElement)
block3.appendChild(innerElement)
結果畫面變成和我們剛剛用 forEach 寫的狀況一樣,只有最後一個 .block3 有被 appendChild():
想必 appendChild() 是有蹊蹺!

使用 appendChild 要注意的小細節

為什麼會這樣呢?其實在使用 appendChild 時,有一個很需要留意的小細節,讓我們來看一下 MDN 怎麼說:
要留意的是 如果 appendChild 使用時,append 上去的是一個已存在的 node 時,它會做的是搬移,而非複製
這是什麼意思呢?以剛剛的程式碼為例:
// 把 innerElement append 到 block1 上
block1.appendChild(innerElement)

// 這時候 innerElement 已經是存在的 Node 了,所會把這個 Node 進行"搬移",於是原本在 .block1 的 innerElement 被搬到 .block2
block2.appendChild(innerElement)

// 同理,原本在 .block2 的 innerElement 被搬到 .block3
block3.appendChild(innerElement)
重點:如果 appendChild 使用時,append 上去的是一個已存在的 node 時,它會做的是搬移,而非複製。
我們可以怎麼證明這一點呢?
我們可以寫一個按鈕,每點一次它就會依序 append 到 .block1, .block2, .block3 來看看變化:
<!-- pug -->
button(type="button" id="appendNode") 切換 appendChild
let i = 0
const buttonAppend = document.querySelector('#appendNode')
buttonAppend.addEventListener('click', function(){
    console.log(i)
    if (i === 0) {
        block1.appendChild(innerElement)    
    }  else if (i === 1) {
        block2.appendChild(innerElement)
    } else {
        block3.appendChild(innerElement)
    }
    i = (i + 1) % 3        // i 會在 0 ~ 2 之間依序循環
})
操作的畫面會像下面這樣,你可以看到當我們把 innerElement appen 到 .block2 時,innerElement 就會從 .block1 被搬到 .block2,同理,也會從 .block2 搬移到 .block3:

使用 Node.cloneNode() 複製 Node Element

從剛剛的範例中,我們可以看到當我們使用 appendChild() 時,對於現存的 Node 它會採用搬移的方式,讓如果我們是想要複製一整個 element 呢?
在 MDN 中也提供的貼心的說明,告訴我們可以使用 Node.cloneNode() 這個方法:
Node.cloneNode() 的用法很簡單,在括弧中可以帶一個參數,true 的話表示深層複製(也是就不只複製 tag,還會複製裡面的內容),讓我們來試試看。可以看到這次 Node 不會是搬移,而是不斷的複製新的 Node:
重點:如果 appendChild 使用時要複製而非搬移,記得先使用 Node.cloneNode() 這個方法複製 Node Element。。
<!-- pug -->
button(type="button" id="cloneNode") 添加 cloneNode
在這裡我們多了一句 cloneElement = innerElement.cloneNode(true) 這樣就會真的複製這個 Node,然後在 appendChild() 進去,而不是搬移同一個 Node。
const buttonClone = document.querySelector('#cloneNode')
buttonClone.addEventListener('click', function () {
    let cloneElement = innerElement.cloneNode(true)
     if (i === 0) {
        block1.appendChild(cloneElement)    
    }  else if (i === 1) {
        block2.appendChild(cloneElement)
    } else {
        block3.appendChild(cloneElement)
    }
    i = (i + 1) % 3
})

程式範例

appendChild 小細節 @ PJCHENder CodePen

參考資料

Share:

2017年6月13日 星期二

[學習筆記] Chrome Dev Tools 開發者工具實用功能整理

重點速記

基本 console 使用

/**
 * 幫助視覺化呈現
**/
console.warn('<output>')
console.error('<output>')
console.info('<output>')
console.assert([Condition Expression], '<output>')
console.clear()

console.dir([DOMElement])
console.table([object/array])


/**
 * 將輸出資料分群顯示
**/
console.group('<groupName>')             // 開始分群,預設展開
console.groupCollapsed('<groupName>')    // 開始分群,預設不展開
console.groupEnd()                       // 結束分群

/**
 * 使用 '%c' 幫輸出的內容添加樣式
**/
console.log('%c What a Cool Console', 'font-size: 32px; color: red')

console 特殊功能

// 計數
console.count([String])

// 計時
console.time([String])            // 開始計時
console.timeEnd([String])         // 結束計時

監聽事件

monitorEvents(element [,event])     //    監聽某一元素
unmonitorEvents(element [,event])   //    取消監聽某一元素 
getEventListeners(element)          //    查看某一元素綁定了哪些事件

選擇 DOM 元素

$0                 // 表示當前所選元素
$(selector)        // 等同於 document.querySelector()
$$(selector)       // 等同於 document.querySelectorAll()

Console 的使用

常用的 console 指令

有一些可以幫助我們 debug 方便檢是的 console 指令像是console.warn(), console.error(), console.assert(condition, '<output'>)

檢視 HTML 元素

使用 console.dir(<HTMLElement>) 可以幫助我們檢視這個 DOM 元素中的所有屬性:

將輸出的資料群組起來

假設我們有一組色票檔:
let colorData = [
    {
        name: 'facebook',
        colorCode: '#4267b2'
    },
    {
        name: 'green',
        colorCode: '#41CEC0' 
    },
    {
        name: 'vue',
        colorCode: '#41b883' 
    }
]
我們可以使用 console.group() 搭配 console.groupEnd() 來將輸出的資料分群,像是這樣:
colorData.forEach(color => {
  console.group(color.name)
  console.log('name', color.name)
  console.log('hexi', color.colorCode)
  console.groupEnd()
})
輸出的結果會像這樣:
如果我們希望預設群組的結果是關閉的,則可以使用 console.groupCollapsed(),需要的時候在打開來看:
colorData.forEach(color => {
  console.groupCollapsed(color.name)
  console.log('name', color.name)
  console.log('hexi', color.colorCode)
  console.groupEnd()
})
輸出的結果會像這樣:

檢視 AJAX Request

在 console 視窗中點右鍵,勾選 “Log XMLHTTPRequest” 就可以看到該網站所發出的 AJAX request:

讓瀏覽器可以直接編輯網頁

在 console 中輸入
document.designMode = 'on'                               
點選網頁文字會直接出現游標,可以直接編輯:

改變 console.log 的樣式

我們可以在 console.log() 的函式中使用 %c,後面再放入 CSS 樣式,就可以改變 console.log 輸出的文字樣式:
console.log('%c What a Cool Console', 'font-size: 32px; color: red')

Debugger 的使用

Debug 流程

  1. 發現(重現)問題
  2. 使用 Sources -> Event Listener Breakpoints 選擇要中斷的事件
  3. 審視原始碼,找出可能有問題的函式
  4. 在該函式可能有問題的程式碼行數的地方點一下,設定斷點(breakpoint),當程式碼執行到該處時,會出現相關訊息。
  5. 利用 watch 輸出某變項或 expression (類似 console.log),利用 console 視窗確認問題
  6. 找出問題後,直接在 source 的區塊內修改程式碼,修改好後按 cmd + S 儲存。
  7. 取消斷點,再次執行,看是否修正錯誤。

設定斷點(BreakPoints)

如果想要看該元素到底是觸發了什麼 JavaScript 事件,可以在該元素上面設定斷點。這裡因為點選該文字後會導致該元素的 CSS 屬性改變,因此我們選擇監聽 attribute modifications
如此當你點選該文字觸發 JS 事件時,Debugger 就會停在這裡:

使用 Debugger

Step over Line of Code (step over)

類似逐步執行的概念。在 A 停下來,按「Step Over Next Function Call」,則會執行B、C,最後停在 D:
function updateHeader() {
  var day = new Date().getDay();
  var name = getName();     // A
  updateName(name);         // D
}
function getName() {
  var name = app.first + ' ' + app.last; // B
  return name;              // C
}

Step into Next Function Call (step into)

進入該函式,在 A 停下來,按「Step Into Next Function Call」之後,會停在 B。
function updateHeader() {
  var day = new Date().getDay();
  var name = getName(); // A
  updateName(name);
}
function getName() {
  var name = app.first + ' ' + app.last; // B
  return name;
}

Step out of Current Function (step out)

跳出該函式。停在 A ,按下 step out,會執行 getName ( ) 中剩下的 code(B),然後停在 C:
function updateHeader() {
  var day = new Date().getDay();
  var name = getName();
  updateName(name); // C
}
function getName() {
  var name = app.first + ' ' + app.last; // A
  return name; // B
}

監控事件處理(monitor event)

除了前面提到的可以在 attribute modifications 設定斷點來偵測觸發的事件外,我們也可以使用 Chrome 內建監聽事件的功能:

開啟事件監聽

我們可以在 console 中輸入 monitorEvents(element, [event]),後面的 event 是 optional 的,如果沒有填的話,它會監測該 element 被觸發的所有事件,一旦事件被觸發就會出現在 console 中:

關閉事件監聽

監聽完事件後,我們可以使用 unmonitorEvents(element, [event]) 來關閉事件監聽,後面的 event 一樣是 optional 的,沒有填的話,會關閉監聽該 element 的所有事件。

查看元素被綁定的事件

另外,我們也可以使用 getEventListeners(element),來看看某一個 DOM 元素被綁定了哪些事件:

其他功能

清除記錄

在 console 中,一般我們可以直接使用 console.clear() 來清除畫面,但是在 chrome 裡面會自動幫你記憶一些你打過的字,有時候自動填補的字並不是自己想要的,這時候我們可以在 console 視窗上點右鍵,選擇clear console history 就可以清除在 console 中曾經輸入過的歷史紀錄:

選擇 HTML 元素

在 chrome 中我們可以像使用 jQuery 一樣,使用 $ 當做選擇器,$ 表示的是 document.querySelector([selector]);如果要選擇多個 DOM 元素,要使用 $$,表示的是 document.querySelectorAll([selector])
另外,你也可以在 element 視窗中點選一個 DOMElement,接著在 console 中輸入 $0,一樣可以選到該元素,$0 表示的是當前 chrome 所選取到的元素:
輸入 $0 可以選到當前在 element 選擇的 DOM 元素:

查看物件

在 console 中我們可以使用 keys(obj)values(obj) 來取的物件的鍵和值:

其他尚未詳細整理的功能

/**
 * Handling Error and Exceptions
**/

console.trace()                                    //    印出目前的 JavaScript call stacks

window.onerror = function(message, url, line){     //    當有錯誤沒有被 try catch 補捉,就會促發 window.onerror
    console.log(`window.onerror is invoked with 
    message = ${message}, url = ${url}, at line = ${line}`)
}

/**
 * Debug
 **/
debug(function)                                   //    將某函式進入 debug 模式
undebug(function)                                 //    將某函式退出 debug 模式
monitor(fn)                                       //    監聽某一函式,會回傳函式名稱和使用的參數

/**
 * Output
 **/
dir(obj)                                          //    條列出物件,等同於 console.dir()
table(data [,columns])                            //    以資料表的方式調列出物件
inspect(obj/fn)                                   //    查看某元素或函式

參考資料

Share:

2017年6月12日 星期一

[Vue] 使用 Vue 實做 Facebook 登入(login)

Facebook 登入的功能幾乎是現在每個網站基本必備的,但個人過去沒有實做過,利用這個機會剛好看一下 Facebook 的 API,利用 Vue 實做出一個簡單的登入頁面,並且簡單記錄。

使用 Vue cli

下載 vue-cli 工具
npm install -g vue-cli
執行 vue-cli 幫我們安裝好環境設定,在這裡把 project 名稱設為 simple-facebook-login
vue init webpack <project>
這時候移動到專案資料夾中,就可以看到 vue-cli 已經幫我們設定好很多開發時需要的檔案了:
其中 buildconfig 資料夾中都是一些 vue 和 webpack 的設定檔,有興趣進一步瞭解 vue-cli 的話可以參閱 Vue-cli @ Vue。
接著執行 npm install 來安裝相關套件
安裝完套件後,執行 npm run dev 就可以看到網頁了:
本篇文章使用的是 vue-cli v2.8.2

安裝 SASS/SCSS

在正式開始開發前,因為我個人習慣用 scss ,所以另外會安裝 sass-loadernode-sass 這兩個套件:
npm install sass-loader node-sass
安裝完之後,我們就可以在 .vue 檔中使用 sass/scss 來編輯了,只需要在 <style></style> 中加上 lang=scss 的屬性就可以使用了,另外也可以加上 scoped 這個屬性,確保每一個 .vue 檔所撰寫的 scss 是不會互相干擾的:
<style lang="scss" scoped>
#app {
    font-size: 40px;
}
</style>

註冊新增 fb 應用程式

接著到 Facebook Developer 的網站去新增一個自己的應用程式
寫下你的 app 名稱
接著會進入到「新增商品」的頁面,這裡我們選擇「Facebook 登入」旁邊的開始使用
接著可以點選「快速啟動」:
這裡我們要做的是 web-apps,所以選擇網站:
輸入網站,在這裡因為我們是在本地端做測試,所以網站的部分寫localhost:8080,按下 Save 後就可以繼續:
接著它會提供我們 Facebook 的 JavaScript SDK 讓我們使用,我們在來就要把這段程式碼放到 Vue 當中使用:

在 Vue 中使用 FB sdk

整個 sdk 包含兩個部分:
  • 先看下半段用 () 包住的 function,這是以 IIFE 的方式載入 facebook 的 JavaScript SDK,如果有需要的話,可以把 js.src 中間的語言選項改成繁體中文(zh-TW),預設是英文(en_US)。
  • 程式碼上半部 window.fbAsyncInit 則是非同步的方式初始化 Facebook SDK。

<script>
  window.fbAsyncInit = function() {
    FB.init({
      appId      : '<app-id>',
      cookie     : true,
      xfbml      : true,
      version    : 'v2.8'
    });
    FB.AppEvents.logPageView();   
  };

  (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "//connect.facebook.net/zh_TW/sdk.js";
     fjs.parentNode.insertBefore(js, fjs);
   }(document, 'script', 'facebook-jssdk'));
</script>
我們可以把程式碼下半部的 IIFE 直接在 main.js 中載入,或者放在 assets 中另外 import 近來,在這裡為了方便管理,我把它放在 ./src/assets/application.js 中:
// .src/assets/application.js

(function(d, s, id){
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) {return;}
  js = d.createElement(s); js.id = id;
  js.src = "//connect.facebook.net/zh_TW/sdk.js";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
接著記得在 main.js 中去載入這支 sdk:
// .src/main.js
import './assets/application.js'
然後在 App.vuemounted hook 中去初始化這支 sdk:
//  ./src/App.vue

mounted () {
    window.fbAsyncInit = function() {
      FB.init({
        appId      : '<app-id>',
        cookie     : true,
        xfbml      : true,
        version    : 'v2.9'
      });
      FB.AppEvents.logPageView();
      console.log('fbAsyncInit')
    };
}
做完 facebook sdk 初始化之後,就可以開始來用這個 sdk 了。

確認使用者 FB 登入狀態

在使用者進來我們的頁面後,要先確定對方有沒有已經登入 Facebook,可以用 sdk 提供的 FB.getLoginStatus(callback<response>) 這個方法來取得使用者登入的狀態。
FB.getLoginStatus(callback<response>) 這個方法裡面要帶一個 callback function ,並且代入一個參數用來取得使用者登入的狀態,這裡我把 fb 回傳的參數稱作 response,我們一樣把程式寫在 mounted 中:
mounted () {
  // facebook 初始化
  window.fbAsyncInit = function() {
    FB.init({
      appId: '<app-id>',
      cookie: true,
      xfbml: true,
      version: 'v2.9'
    });
    FB.AppEvents.logPageView();

    // Get FB Login Status
    FB.getLoginStatus( response => {
      console.log('res', response)        // 這裡可以得到 fb 回傳的結果
    })
  };
}
在 facebook 回傳的 response 中包含幾個屬性:

{
    status:'connected',
    authResponse:{
        accessToken:'...',
        expiresIn:'...',
        signedRequest:'...',
        userID:'...'
    }
}
其中比較重要的 status 包含幾個狀態:
  • connected: 使用者已登入 FB,且授權你的 app 使用。
  • not_authorized: 使用者已登入 FB,但未授權你的 app 使用。
  • unknown: 使用者沒有登入 FB,或已從你的 app 中登出。
如果你拿不到 authResponse 有可能是使用者沒有登入 FB,或者他尚未授權你的 app 使用。

使用 FB 登入

接著我們要讓使用者透過 FB 登入,這時候會用到 FB.login() 這個方法,在 FB.login(callback<response>, options) 中,一樣可以代入一個 callback function 來取得 response。
後面的 options 則是以物件的方式代入想要取得權限的資料,這裡我們可以使用 scope 取得使用者的 public_profileemail
設定 return_scopestrue 時可以得到被授權的清單,可以在使用者登入的 response 中多得到一個名為 grantedScopes 的屬性,裡面會告訴你授權了哪些可用的項目,例如:“email,public_profile”。
我們把 login 的 function 寫在 Vue 的 methods 中:
//  ./src/App.vue

methods: {
  login () {
    let vm = this
    FB.login(function (response) {
      console.log('res', response)
    }, {
      scope: 'email, public_profile',
      return_scopes: true
    })
  }
}
並且綁在 HTML 的 Login button (@click="login")上:
<!--  ./src/App.vue  -->

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
    <div class="container">
      <div class="row justify-content-center">
        <div class="col-2">
          <button type="button" class="btn btn-outline-success" @click="login">Login</button>
        </div>
        <div class="col-2">
          <button type="button" class="btn btn-outline-success">Logout</button>
        </div>
      </div>
    </div>
  </div>
</template>
這時候當你點選登入的按鈕時,應該會跳轉到 Facebook ,出現下面的畫面
按下登入後這個視窗就會關閉,然後回到你的 app。
然後 app 就可以取得最新的登入狀態,這時候沒有問題的話應該會是 connected
但是這時候你會發現在回傳的 response 中卻沒有拿到 email,這時候我們需要使用 Graph API 來取得使用者的 email 和其他資訊。

取得使用者資訊

如果我們想要取得使用者的 email 或其他資訊,必須使用 Facebook Graph API,FB.api(path, method, params, callback),Graph API 的基本用法可以用 query 來取得你想要的欄位,在這裡我們想要取得使用者的 name, id, 和 email
FB.api('/me?fields=name,id,email', function (response) {
  console.log('res in getProfile', response)
})
我們把取得使用者資料的部分獨立成一個 funtion,稱作 getProfile,當使用者 login 之後,我們就去執行這個方法:
methods: {
  getProfile () {
    FB.api('/me?fields=name,id,email', function (response) {
      console.log('res in graphAPI', response)
    })
  },
  login () {
    let vm = this
    FB.login(function (response) {
      console.log('res when login', response)
      vm.getProfile()
    }, {
      scope: 'email, public_profile',
      return_scopes: true
    })
  }
}
這時候使用者登入時,我們就會呼叫 getProfile() 這個方法,然後就可以得到和使用者相關的資訊:

FB 登出 APP

在登入之後,我們可以用 FB.logout() 這個方法來登出 app。我們一樣把登出寫成一個方法,並且綁訂在 logout 這個 button 上:
//  ./src/App.vue
logout () {
  let vm = this
  FB.logout(function (response) {
    console.log('res when logout', response)
  })
},
<!-- ./src/App.vue -->
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
    <div class="container">
        <div class="row justify-content-center">
          <div class="col-2">
            <button type="button" class="btn btn-outline-success" @click="login">Login</button>
          </div>
          <div class="col-2">
            <button type="button" class="btn btn-outline-success" @click="logout">Logout</button>
          </div>
        </div>
    </div>
  </div>
</template>
在使用者按下登出按鈕後,status 就會變成 unknown:

當使用者登入狀態改變時

習慣上我們會多一個方法,這個方法是當使用者狀態改變時,我們就會執行它,這裡把這個方法稱作 statusChangeCallback
我們先在 data 中新增兩筆被 Vue 監控的資料:
function data () {
  return {
    profile: {},
    authorized: false
  }
}
在方法中則增加 statusChangeCallback ,每當使用者登入的狀態變更時,就會執行這個方法:
methods: {
    function statusChangeCallback (response) {
      let vm = this
      if (response.status === 'connected') {
        vm.authorized = true
        vm.getProfile()
      } else if (response.status === 'not_authorized') {
        vm.authorized = false
      } else {
        vm.authorized = false
      }
    }
}

程式碼整理

讓我們完整的整理一下 App.vue 這支程式,首先 Template 的部分:
我們在用 v-if 來判斷使用者目前的登入狀態,如果已經登入,則顯示 v-else 中的 logout button;如果尚未登入,則顯示 login button。
在 login 和 logout button 則分別綁上 @click="login"@click="logout" 的方法:
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
    <div class="container">
        <div class="row justify-content-center">
          <div class="col-2" v-if="!authorized">
            <button type="button" class="btn btn-outline-success" @click="login">Login</button>
          </div>
          <div class="col-2" v-else>
            <button type="button" class="btn btn-outline-success" @click="logout">Logout</button>
          </div>
        </div>
    </div>
  </div>
</template>
再來是 JS 的部分,我們在 data 的 profile 中會儲存使用者從 FB 登入後回傳的使用者資訊。
  • getProfile: 這裡面用了 Facebook Graph API,另外為了讓資料能夠持續有響應式的變化,我們使用了 vm.$set() (可參考:這個 Vue 的方法為什麼畫面沒有隨資料更新 - Vue 響應式原理)。
  • loginlogout 的方法中,我們都會去呼叫 statusChangeCallback 這個方法,幫助我們判斷使用者目前的登入狀態,並更新 Vue 中的資料。
import Hello from './components/Hello'

export default {
  name: 'app',
  components: {
    Hello
  },
  data () {
    return {
      profile: {},
      authorized: false
    }
  },
  methods: {
    getProfile () {
      FB.api('/me?fields=name,id,email', function (response) {
        vm.$set(vm, 'profile', response)
      })
    },
    login () {
      let vm = this
      FB.login(function (response) {
        vm.statusChangeCallback(response) 
      }, {
        scope: 'email, public_profile',
        return_scopes: true
      })
    },
    logout () {
      let vm = this
      FB.logout(function (response) {
        vm.statusChangeCallback(response)
      })
    },
    statusChangeCallback (response) {
      let vm = this
      if (response.status === 'connected') {
        vm.authorized = true
        vm.getProfile()
      } else if (response.status === 'not_authorized') {
        vm.authorized = false
      } else if (response.status === 'unknown') {
        vm.profile = {}
        vm.authorized = false
      } else {
        vm.authorized = false
      }
    }
  },
  mounted () {
    let vm = this
    
    // facebook 初始化
    window.fbAsyncInit = function() {
      FB.init({
        appId: '135862346985755',
        cookie: true,
        xfbml: true,
        version: 'v2.9'
      });
      FB.AppEvents.logPageView();

      // Get FB Login Status
      FB.getLoginStatus(response => {
        vm.statusChangeCallback(response)
      })
    };
  }
}
當目前為止,你就已經完成了一個基本能夠用來登入和登出 FB 的 app 了。

進一步優化

到上一個步驟其實就已經完成了 Facebook 登入。我們可以進一步把從 facebook 取得的資料,利用 props 代到 hello.vue 這個 component 中:
<!-- ./src/App.vue -->
<hello :profile="profile"></hello>
Hello.vue 中我們可以利用從 Facebook 傳來的資訊取得使用者的大頭照,利用 v-showprofile.name 存在時,就顯示只用者的大頭照:
<!-- ./src/components/Hello.vue -->
<template>
  <div class="hello">
    <img v-show="profile.name" :src="profilePicture" alt="profile" class="profile-picture"/>
    <h1 v-html="msg"></h1>
  </div>
</template>
這裡我們使用 props 來取得從父組件傳進來的資料,在 computed 中利用回傳 msg() 這個資料;另外用 profilePicture() 來取得使用者的大頭照:
export default {
  props: ['profile'],
  name: 'hello',
  computed: {
    msg () {
      if (this.profile.name) {
        return `Welcome <b><i> ${this.profile.name} </i></b> to Vue.js App`
      } else {
        return 'Login Facebook to Enjoy the App'
      }
    },
    profilePicture () {
      return (this.profile.id) ? `https://graph.facebook.com/${this.profile.id}/picture?width=300` : `/static/man.gif`
    }
  }
}

完成作品

一開始進入的畫面長的像這樣子:
當使用者點選 “Login” 後會切換到如下的畫面:

程式碼內容

如果想要檢視完整的程式碼可以到 程式碼內容 @ Github

參考資料

Share: