顯示具有 vue 標籤的文章。 顯示所有文章
顯示具有 vue 標籤的文章。 顯示所有文章

2017年11月3日 星期五

Vue multiselect set autofocus and tinymce set autofocus

要在畫面一進來 focus multiselect 的方式:

參考: https://jsfiddle.net/shentao/mnphdt2g/
主要就是在 multiselect 的 tag 加上 ref (例如: my_multiselect),
另外在 mounted() 執行:
this.my_multiselect.$el.focus();


------
要在畫面進來 focus tinymce 的方式:

關鍵字請搜尋 tinymce  init_instance_callback
https://stackoverflow.com/questions/19829126/tinymce-4-how-to-put-cursor-to-end-of-the-text
這就是解法
還會判斷是不是有內文,有內文的話 會 cursor 到最後一的字後面。

2017年9月21日 星期四

Vue extends 與 mixins 的不同


photo crop from https://unpkg.com/vue@2.3.0/dist/vue.js

好久以前,大概半年前我用了 Vue Typehead 這個第三方套件,那時候就按照 readme 直接用 extends 用上,半年後我加了好多新功能,把一樣的 methods, data property 拆出去變成 mixins。

extends: VueTypeahead,
----
mixins: [ hasPager, hasSearch ],

今天在整理 code 的時候,就好奇這兩個差別是什麼,看了一下 source code,好像是一樣的作用,最後都是呼叫 mergeOptions,只是 mixins 可以帶很多個 (因為是 Array),extends 就一個。

Vue 註冊全域的 Component

網站上有很多元件非常容易重複使用,例如 Loading 狀態,Alert 文字框,無資料的狀態,分頁等 Component,既然這些元件會時不時的被使用,那為何不直接註冊成 Global Component ?!,這樣就再也不用在每一頁重新 import 一次那些常用的 component 了。

在 new 一個 Vue 之前處理好這些 import 跟 註冊 component

// main.js, 匯入所有需要註冊成全域的 component

import Paginate from 'vuejs-paginate'

import Loading from './components/Loader'

import EmptyStatus from './components/EmptyStatus'

// Register as global component
Vue.component('paginate', Paginate)

Vue.component('loading', Loading)

Vue.component('empty-status', EmptyStatus)

//...
new Vue({
el: '#app',
....
});

接著就可以直接在 component 直接使用~

2017年9月19日 星期二

Vue-Resource finally

在 Vue / Vue-Resource 接 api 時候,前陣子有一件事一直很困擾我,那就是 Promise 沒有一個狀態讓我知道已經完成,不論 request 是否成功或失敗,我只能知道 resolve, reject, cache... ,但我希望有一個像 jQuery 的 always 一樣,能夠給我放一個 callback 在 request 完成時呼叫 (不論結果如何)。

今天看到一篇文章 (Finally the Promise.prototype.finally() is available),說 Promise 的 finally 已經在 TC39 ECMAScript Proposal 裡面,現在急要用的話可以走 polyfill,該文章有幾種 polyfill 的做法,可以參考看看。不過我看 Vue-Resource 的原始碼,已經有幫我處理過 finally 的樣子,因此我也沒有特別做 polyfill。

舉個之前為什麼需要 finally 的例子,因為在發 api 之前需要顯示 loading 轉轉轉,但是不管 api 的 http status 是 200 還是其他,在結束 ajax 之後,都應該關掉 loading 轉轉轉:

loading.show();

ProductService.getCategory()

 .then((res)=> {

  // success

  this.category = res.body;

  loading.close();

 }, (res)=>{

  // error

  this.category = null;

  loading.close();

 });


寫了兩次的 loading.close 感覺就很麻煩,因此有了 finally,只需要一次:

loading.show();

ProductService.getCategory()

 .then((res)=> {

  // success

  this.category = res.body;

 }, (res)=>{

  this.category = res.body;

  // error

 }).finally(() => {

  loading.close();

 });

如果有其他做法可以分享也歡迎留言~

參考:
https://stackoverflow.com/questions/35999072/what-is-the-equivalent-of-bluebird-promise-finally-in-native-es6-promises
https://hospodarets.com/promise.prototype.finally
https://github.com/pagekit/vue-resource/issues/262

2017年9月18日 星期一

使用 Vue Resource 的 interceptors 處理不同的 http status code

首先你的 Vue 專案也是使用 Vue Resource 處理 http request, 再往下看~

在 SPA 因為有大量接 API 的需求,但每次都要在獨立的 component 處理不同的 http status code 狀態,實在有點麻煩,比方說遇到 api 的 status 是 401 的話,就要再寫一個 router push 到使用者登入畫面之類的 code,重複的邏輯散落在各處 page component.

因為 vue resource 有個好用的 api 叫做 interceptors,覺得他跟 Laravel 的 middleware 很相似,就是所有 request 都會經過他,做一些處理,因此可以利用 interceptors 過濾每一次 request 的狀態,假如是 401 就導到 login, 假如是 404 就導過去 not found 頁面。

確認有使用 vue resource:

import VueResource from 'vue-resource'

確認有 use 它:

Vue.use(VueResource)

在 use 之後:

Vue.http.interceptors.push((request, next) => { 
 next((response) => {
  if (response.status === 401) {

   console.log('權限不足');

   // do logout

   router.push({ path: '/login' });

  }

  if (response.status === 404) {

   router.replace({ path: '/404' });

  }

 });
});

參考: https://stackoverflow.com/questions/37228087/vue-js-interceptor

Vue Router 實作按需加載,將 JavaScript 拆成好幾隻

一開始使用 Vue 寫 Single Page Application (SPA),頁面越來越多時,編譯出來的 build.js 越來越肥,覺得這樣 request 時 loading 蠻吃緊的,最高甚至到了 3MB 以上,雖然有 nginx 的 gzip 在前面擋著,不過還是有其他方式可以盡量減少檔案大小,因此就打算針對某些 routes 做成按需加載,也就是有走到某個頁面時,再加載該頁面的 JavaScript。

*我的專案是 Vue2 / Webpack2 / Vue-Router
1.  首先要在 routes 的 import 寫法改一下,把需要按需加載的頁面,改成 resolve require 的方式:
/*
import Home from '../pages/Home'
import Product from '../pages/Product'
import Search from '../pages/Search'
*/
// 改成
const Home = resolve => require(['../pages/Home'], resolve)

const Product = resolve => require(['../pages/Product'], resolve)

const Search = resolve => require(['../pages/Search'], resolve)

如果是 webpack2 可以寫成

const Home = () => System.import('../pages/Home')


2. 接著記得要在 webpack 的設定,加上 publicPath 的設定,這樣做當需要按需加載時,才知道網址要怎麼帶,否則網址會是錯的。output.publicPath 怎麼寫可以參考哦! 。

假如 publicPath 沒辦法 hard code 在 webpack 設定怎麼辦?  因為每個人開發機的網址可能都不同,這邊的替代方案叫做 configuring webpack public path at runtime,也就是不要把 publicPath 設定寫在 webpack, 改為寫在程式裡,當程式要 build 的時候,動態設定 publicPath,怎麼做呢?

在 main.js 加上:

__webpack_public_path__ = `${SiteConfig.DOMAIN}front/build/`;

這個部分可以參考。看有沒有需要再用 XD

完成這些設定就可以重跑看看 webpack build/watch
會發現檔案會用數字拆開成好幾隻

[僅為示意]
                     4.js   169 kB       4  [emitted]
                     5.js  20.9 kB       5  [emitted]
                     6.js  11.8 kB       6  [emitted]
                     7.js  11.8 kB       7  [emitted]

接著打開瀏覽器,走訪一下那些改為 resolve require 的網頁路徑,就會看到他們會額外加載那一頁的 JavaScript (至於數字是怎麼安排的我也不知道就是惹 XD)



參考
https://router.vuejs.org/en/advanced/lazy-loading.html
vue-router 三种加载方式速度评测 http://meiminjun.github.io/vue-router/

假如一開始檔案就小小的,也不必要走這種 code split 的方式,load 一隻 build.js 的好處就是只會發一次 request XD 分好多隻主要是因為整包太肥,不希望使用者一連進來就要等好久才看得到畫面。

利用 Vue.prototype 來設定全域變數

在 Vue component 裏,隨時都有需要拿取一些設定的值,例如圖床的 domain,因為要組完整的圖片 url,例如某些功能是否要開啟,例如檔案大小的 MB 限制也要寫成設定,例如 api 的 endpoint,網址也不可能是 hard code, 也是統一透過一個設定而來。

寫成獨自的設定,不依賴後端傳設定,方便脫離後端。

我的做法是寫一隻 config,但是又需要丟進去給 Vue,
又不想直接 assing 給 window 物件 (如果要這麼公開的直接丟給 window,根本也不用寫 config.js,也很容易看到四散在各處的 window.xxx = 某個設定的值)
所以我就用了 Vue.prototype,集中管理所有跟設定有關的部分,傳給 Vue。

首先在專案的目錄的某處,放置這隻充滿設定的檔案,檔名我也叫 config.js


const CONFIG = {

 /**

  * [XSRF_TOKEN]

  * @description: xsrf_token

  * @docs: https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie

  */

 XSRF_TOKEN: decodeURIComponent(document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, "$1")),

 /**

  * [API_URL]

  * @description: 全站發 api 的 endpoint

  */

 API_URL: 'https://yiyingloveart.blogspot.com/demo',

 PHOTO_URL: 'https://static.xxxx.xxx/'

}

export { CONFIG };

接著在專案的主程式 以我來說是 main.js,import config 那隻檔案,另外 import 後命名為 SiteConfig. (*看個人習慣)

import { CONFIG as SiteConfig } from './config'

接著設定為 $conf,這樣做的方式跟 Vue.prototype.$conf = SiteConfig; 一樣

// inject all config for $conf

Object.defineProperty(Vue.prototype, '$conf', { value: SiteConfig });

接著就可以在整個站的 component 直接透過 this.$conf.YOUR_CONFIG 或者 $conf.YOUR_CONF 直接取得設定值


$ 命名
為什麼不叫 conf 而要 $conf, 其實要不要加 $ 這沒有一定,主要是用來提醒開發者,這個物件可能是共用設定或是第三方套件之類,或是一些保留使用的命名。就像 vue 也有 $mount, $ref 一樣,這些命名可以直覺得一眼感受到是共同使用的。

在 template 使用不需要 this, 在 script 裡面需要 this

例如:

在 template 用到:

<img :src="$conf.PHOHO_URL....">

在 script 用到:

if (this.$conf.ENABLE_FACEBOOK === true) {
   // do something!
}


如果有其他做法也歡迎留言讓大家知道!


參考
https://vuejsdevelopers.com/2017/04/22/vue-js-libraries-plugins/
[知乎] Vue.js 如何添加全局函数或变量?

在 Vue 使用 SweetAlert2

本文假設你已經裝好 Vue 以及 Webpack 等設定。
首先需要安裝 sweetalert2:

npm i sweetalert2 --save-dev

在主要的程式進入點,例如: main.js (看個人摟) 做 import:

import swal from 'sweetalert2'

接著在下方定義 swalPlugin
另外把 sweetalert2 的設定指回去給 Vue.prototype.$swal
你也可以定義別的名稱,例如 Vue.prototype.$sweetalert2 之類的。

const swalPlugin = {}

swalPlugin.install = function(Vue){

 swal.setDefaults({

  confirmButtonClass: 'btn btn-primary',

  cancelButtonClass: 'btn btn-default',

  buttonsStyling: false,

  showCloseButton: true
 });

 Vue.prototype.$swal = swal;
}

Vue.use(swalPlugin)

setDefaults 這些設定可不是空穴來風,他只是用來設定一些全站使用 sweetalert 預設的參數,(sweetalert 官網有寫,當然在此範例裡,你也可以都不要有任何設定。)

使用 Vue.use 完成註冊之後,接下來你就可以在整個站用 this.$swal,直接這樣呼叫使用 sweetalert2 了。 例如:


this.$swal({

 text: '你確定要刪除嗎',

 type: 'question',

 showCancelButton: true,

 confirmButtonText: '確定',

 cancelButtonText: '取消',

}).then(function () {

 // 確定要做的事

}, function() {
        // 取消要做的事
});


這是我的 main.js 局部:


雖然好像也有好心人推出 vue sweeralert 的套件,但因為我在用的時候還沒有人寫 XD
但我覺得自己匯入這樣的第三方套件 比較知道做了哪些事情 還蠻方便 也不用在 package.json 再多上一筆用 vue 包裝的第三方套件。

有任何其他做法的建議,也歡迎留言讓小的知道! 感謝感謝!

其他參考文章
http://www.codebyjigs.com/vue-plugin-sweetalert2/
https://vuejsdevelopers.com/2017/04/22/vue-js-libraries-plugins/

2017年4月24日 星期一

為 Vue 專案加上 eslint

背景
vue2 + webpack2

所需要的 package
npm i babel-eslint eslint eslint-config-vue eslint-loader, eslint-plugin-vue --save-dev

webpack loader 調整為:
{
// 轉 ES6 以及 處理 Eslint 檢查
test: /\.js$/,
use: [
'babel-loader',
'eslint-loader'
],
include: [
     path.join(__dirname, './resources/assets/js/')
]
},

include 可以換成 exclude, 如果你的專案結構很簡單的話。

另外在 loaderOptionsPlugin 調整為:

vue: {
loaders: {
js: 'babel-loader!eslint-loader',
scss: 'style-loader!css-loader!sass-loader'
}
},
eslint: {
configFile: './.eslintrc'
}

接著在根目錄建立 .eslintrc 檔案,他可能這樣,你可以再複寫你的設定:

{
"extends": ["vue"],
"plugins": ["vue"],
"rules": {
"vue/jsx-uses-vars": 2,
"no-unused-vars": 0
  }
}



參考:
在Vue+Babel+Webpack环境中使用ESLint
https://github.com/vuejs/eslint-plugin-vue

2016年6月20日 星期一

Vue 用 porps 把父層的資料傳給 child component

just an example,
HTML:
<!--準備 CardComponent 的 Template-->
<script type="x-template" id="cards-tmp">
  <div id="cards-tmp">
    <div class="card" v-for="card in cards">
      <h2>{{ card.title }}</h2>
      <p>{{ card.description }}</p>
      <a href="#" v-on:click.prevent="openModal(card.id)">read more</a>
  </div>
</script>
  
<div id="app">
  <h1>Vue App Example</h1>
  <div>
    <card-comp :cards="cardOriginal"></card-comp>  
  </div>
</div> 

JS
// make an component
var cardComponent = Vue.extend({
  // 指定是哪個 Element
  template: '#cards-tmp',
  // what props you want to accept.
  props: ['cards'],
  methods: {
    openModal: function(id){
      alert(id);
    }
  }
});

new Vue({
    el: '#app',
    components: {
      // register name is card-comp
      'card-comp': cardComponent
    },
    data: {
      cardOriginal: [
        {
          id: 123,
          title: 'Hello Vue',
          description: 'Good Morning!'
        },
        {
          id: 123,
          title: 'Hello Vue',
          description: 'Good Morning!'
        }
      ]
    }
  
});

jsbin:
JS Bin on jsbin.com

參考:
https://github.com/vuejs/vue/issues/1987

2016年6月19日 星期日

Vue Avatar 解決了我覺得大頭照那個位置一直很空洞的問題

Avatar 是大頭照的意思。其實之前在用 PHP 就發現有類似的 package (以 Laravel 來說,就有像是 laravolt/avatar),可以爬出一段使用者的名稱,回傳文字的簡寫。然後隨意找找還真的讓我找到 vue 也有好心的開發者作出產出 avatar 的 component。

Git Repo Demo 連結: https://eliep.github.io/vue-avatar/
Git Repo: https://github.com/eliep/vue-avatar

不過我還是用 ES5 的方式在使用,步驟如同官方說明。

載入 vue 以及 vue avater 的 js 檔案
<script src="/assets/js/vue.min.js"></script>
<script src="/assets/js/vue-avatar.min.js"></script>

除了基本的載入 vue-avatar.min.js 之外,還要在 vue 初始化一個 instance 的地方加上 components 的設定,例如:
new Vue({
  el: '#app',
  components: {
    'avatar': Avatar.Avatar
  },
  // 來一點示範的資料
  data: {
    username1: 'Win.Wu 吳穎穎',
    username2: 'YIYING WU',
    username: "hello world",
  },
  ....

在 HTML 的地方:
<avatar :username="username1" :size="40"></avatar>
<avatar :username="username2" :size="40"></avatar>
<avatar :username="username" :size="40"></avatar>

結果:

<avatar> 這個 component 還有很多 attr/props 可以用,設定方式都在 git pages 上,如果你有想要客製化什麼部分,請從文件上找找吧。

只是做一般需要 member 登入的 website,就算是透過臉書也有可能是使用者沒有大頭照的情況,為了不讓那個位置空著,或是放個什麼奇怪的 icon,也許這是一種方式 :P

(我純粹是自己很喜歡而已 :))


2016年6月15日 星期三

Vue Instance Lifecycle Hooks 實例生命週期

一個簡單的 sample,主要我是在看整個 vue  的生命週期,對 vue 有些瞭解了。

var vm = new Vue({
  el: '#app',
  data: {
    name: 'winwu',
    width: 900,
    height: 500,
    barColor: 'red',
    barHeight: 30,
    barXOffset: 10,
    dataset: [30, 20, 45, 12, 21]
  },
  // https://vuejs.org/api/#Options-Lifecycle-Hooks
  init: function() {
    // init 這時候還沒有觀察到 data observation, event, watcher...
    console.log('init: my name is ' + this.name);
  },
  created: function() {
    /* data observation,
     * computed properties,
     * methods,
     * watch/event callbacks.
     * 在這個階段完成
     * 但,$el 這時候還沒有好,(DOM 的解析還沒開始)
     */
    console.log('creadted: my name is ' + this.name);
  },
  beforeCompile: function() {
    // 我也不曉得什麼時候會呼叫到這裡
    console.log('beforeCompile: my name is ' + this.name);
  },
  compiled: function() {
    console.log('compiled: my name is ' + this.name);
  },
  ready: function() {
    // 在 vm.$el 插入 DOM 時呼叫。
    console.log('ready: my name is ' + this.name);
  },
  attached: function() {
    console.log('attached');
  },
  beforeDestroy: function() {
    // 我也不曉得什麼時候會呼叫到這裡
    console.log('beforeDestroy');
  },
  destroyed : function() {
    console.log('destroyed: my name is ' + this.name);
  }
});

// call destroy, 才會觸發 destroyed func. 這時候長條圖已經不會隨著 data 連動
vm.$destroy();

// 第一個參數為是否 remove,若為 true,所有 DOM 的關聯會刪掉。
// vm.$destroy(true);
這是 log 結果:
init: my name is undefined
01.js:29 creadted: my name is winwu
01.js:33 beforeCompile: my name is winwu
01.js:36 compiled: my name is winwu
01.js:43 attached
01.js:40 ready: my name is winwu
01.js:46 beforeDestroy
01.js:49 destroyed: my name is winwu// vm.$destroy(true);

我放在 jsbin 上面,可能比較好讀:

JS Bin on jsbin.com
jsbin: http://jsbin.com/mibiya/1/edit?html,js,console,output
參考官方文件: https://vuejs.org/api/#Options-Lifecycle-Hooks

使用 Vue-cli 快速建立 Vue 專案樣板

Vue 官方提供了一個 vue-cli 工具可以快速建立 vue 的專案,首先你需要以下:
  • Node.js 環境
  • Npm

2016年6月12日 星期日

Vue.js 改變分隔符的符號 Change Custom Delimiters

在使用 vue.js 重構舊有的專案時,發現 {{ variable }} 這樣的 expression 竟然怎麼樣都不顯示在畫面上,原來是因為我的 view 是 hbs (handlebarsjs) (因為是 express 的專案...),所以這 {{ }} 這個分隔符號會先被 handlebarsjs 吃掉,以至於我的變數反而沒被 vue 辨識。

2016年6月9日 星期四

Vue.js 學習資源

我堅持今天一定要寫點什麼東西 (別問我在堅持什麼 XD),所以先來談談 vue.js 這個 Library (暫時我還不知道,該定義它是 Library 還是 Framework,就目前的感覺,我覺得較像 Library )。

2016年6月5日 星期日

Vue.js select 下拉選單示範

這裡是示範一個下拉選單,其中的關係包含被選擇的項目,以及關聯的下一層選單。簡單練習而已,一定還是有更好的寫法。

Vue multiselect set autofocus and tinymce set autofocus

要在畫面一進來 focus multiselect 的方式: 參考: https://jsfiddle.net/shentao/mnphdt2g/ 主要就是在 multiselect 的 tag 加上 ref (例如: my_multiselect), 另外在 mounted...