2019年6月16日 星期日

[Vue.js] v-for設定key的作用與影響


v-for迭代陣列或物件時需要設定key,是為了避免重複產生DOM元素而浪費資源,因此將key視為一個辨識的依據,所有的key必須保持唯一。因此如果key值不小心重複,console甚至會出現Duplicate keys detected: ... This may cause an update error. 這樣的警示訊息。

為了顯示key的作用和影響,用下面的例子來看,程式內容大意是根據menu的內容產生多個product-box,兩個唯一的差別是前著用indexkey,後者則是用id當作key,此外多加一個button來更動資料的內容。
<template>
  <div>
    <product-box v-for="item,index in menu" :key="index" :value="item.id">
      {{ item.name }}
    </product-box>

    <product-box v-for="item in menu" :key="item.id" :value="item.id">
      {{ item.name }}
    </product-box>

    <button @click="addFirstElement">Add</button>
  </div>
</template>

<script>
import ProductBox from './ProductBox'
export default {
  data () {
    return {
      menu: [
        {id: 'A001', name: 'milk tea'},
        {id: 'A002', name: 'juice'}
      ]
    }
  },
  methods: {
    addFirstElement: function() {
      let first_elm = [{id: 'A000', name: 'coffee'}];
      this.menu = first_elm.concat(this.menu);
    }
  },
  components: {
    ProductBox
  }
}
</script>

ProductBox.vue
<template>
  <div>
    {{ dispay_text }} <slot></slot>
  </div>
</template>

<script>
  
  export default {
    data: function() {
      return {
        dispay_text: ''
      }
    },
    props: {
      'value': String
    },
    created: function() {
      this.dispay_text = this.value;
    }
  }
</script>

初始的情況下:
A001 milk tea
A002 juice

接著,點button在開頭插入一個新的資料,預想的顯示如下,如果用id當作key的話也是產生一樣的結果。
// key=id

A000 coffee
A001 milk tea
A002 juice

這時因為用index當作key,所以原本index 1和2因為沒有偵測到變化並不會重新產生,也就是不會重新經歷created,所以出現所謂的update error,結果會變成:
// key=index

A001 coffee
A002 milk tea
A002 juice

雖然用index當作key很方便,但並非所有的情況都適合使用index當作key。

2019年6月10日 星期一

[Vue.js] 共用Domain Name 部屬多個vue專案


接續『[Vue.js] vue-router設定history mode移除網址的#』的內容,可以在web server裡部屬vue的專案,如果要共用同一個domain name,用不同的子目錄(subdirectory)來區分時,只要根據下面的步驟稍微調整你的專案,既不會影響原本的開發模式,也能在正式區讓多個專案同時運行。

假設兩個專案project-Aproject-B分別為:
1. project-A: /var/www/project_a
http://my.project
2. project-B: /var/www/project_b
http://my.project/group/

所有project-B的URL,都會多一個 /group/ 的子目錄;反之,帶有 /group/ 的網址也會自動與project-B的路由進行比對。
project-A的設定方式不再說明,可以參考『[Vue.js] vue-router設定history mode移除網址的#』,下面講解project-B的設定方式。

Step1. vue-router
設定base
// src/router/index.js
 export default new Router({
   base: '/group/',
   mode: 'history',
   ...
 }

Step2. build參數
設定assetsPublicPath,請注意有兩個不同的key,須設定的是buildassetsPublicPath
// config/index.js
 module.exports = {
   dev: {
       ...
   },
   build: {
     assetsPublicPath: '/group',
     ...
   }
 }

Step3. build
npm run build
取得目錄dist/底下的檔案,搬到目的的資料夾/var/www/project_b

Step4. 建立.htaccess
將檔案放到/var/www/project_b,也就是project-B相同的目錄。
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . group/index.html [L]
</IfModule>

Step5. Apache Config
將檔案放到/var/www/html/project_b,也就是project-B相同的目錄。
<VirtualHost *:443>
  ServerName my.project
  DocumentRoot /var/www/project_a
  DirectoryIndex index.html
  <Directory /var/www//project_a>
    AllowOverride all
  </Directory>
  
  Alias /group /var/www/project_b
  <Directory /var/www/project_b>
    AllowOverride All
  </Directory>
</VirtualHost>

sudo systemctl restart apache2
請記得重啟apache2,設定就完成囉!

2019年5月24日 星期五

[Vue.js] 如何在component自訂v-model


v-model 通常用在HTML的 input,根據輸入的內容會跟著改變這個參數的內容,建立專案時會將一些基礎元件(如:input)定義成一個component,方便在各個頁面引入使用。

其實 v-model 是結合了 propsemit ,可以先參考『[Vue.js] 父子元件的雙向溝通,簡單的props和emit使用範例』,比較能理解下面的內容。

舉例常見的用法來看:
<input v-model="username" />
v-model其實綁定了名為 valuepropsinputemit事件,因此所謂的 v-model 就是一個父子元件的雙向溝通,可以拆寫成:
<input :value="username"
       @input="(value)=>(username=value)" />
跟使用 v-model 達到的效果相同。


定義元件的v-model和使用

1. 定義子元件
input.vue
<template>
    <input ref="input"
        class="my-input"
        type="text"
        :placeholder="placeholder"
        :value="value"
        @input="updateValue" />
</template>

<script>
export default {
  props: {
    value: String,
    placeholder: {
      type: String
     }
  },
  methods: {
    updateValue: function() {
      this.$emit('input', this.$refs.input.value);
    }
  }
}
</script>

<style scoped>
.my-input {
    color: #3b3b3b;
    font-size: 1rem;
    line-height: 1.5;
    border: 1px solid #003377;
    padding: 4px;
}
</style>

2. 在父元件使用
<template>
  <div style="text-align: center">
    <h1>coustom v-model</h1>
    <my-input v-model="username" />
    <p>My name is {{ username }}</p>

    <h1>coustom v-model with placeholder</h1>
    <my-input v-model="email" placeholder="input your email" />
    <p>My email is {{ email }}</p>
  </div>
</template>

<script>
import MyInput from './input.vue'
export default {
  data: function() {
    return {
      username: "",
      eamil: ""
    }
  },
  components: {
    MyInput
  }
}
</script>

頁面預覽:


上述只有把 placehodler 加到 props 裡,其他像是typedisabledreadonly等等常用的attr可以依照這個方式加進你的元件裡。另外除了 inputcheckboxradio這些表格常用的也可以一一寫成元件來使用。


自訂props的預設值和監聽事件

預設情況下,v-model 是綁定的 propsvalueinputemit事件,若要自訂名稱請參考:
export default {
  prop: ['keyword'],
  model: {
    prop: 'keyword',
    event: 'transfer'
  }
  methods: {
    updateValue: function() {
      this.$emit('transfer', this.$refs.input.value)
    }
  }
}
在上面的例子裡,propskeyword,綁定的事件則是trasfer,也就是說這邊的 v-model 應該理解成:
<input :keyword="username"
       @transfer="(value)=>(username=value)" />


參考資料:
https://scotch.io/tutorials/add-v-model-support-to-custom-vuejs-component


2019年5月19日 星期日

[Vue.js] 預覽上傳照片的原始碼分享(single/multiple file)


選擇照片上傳前如何實作預覽的功能,下面拆解成3個部分說明:
1. 上傳檔案
首先需要一個上傳檔案的HTML
<input type="file" accept="image/*" @change="previewImage">
根據上面的程式碼,我們需要一個 previewImage 的function來處理選擇的圖片檔。

2. 實作previewImage
接下來是js的程式部分,我們定義了兩個變數 previewimage,前者是存放預覽圖片,後者則是實際檔案。
new Vue({
  data: function() {
    return {
      preview: null,
      image: null
    };
  },
  methods: {
    previewImage: function(event) {
      var input = event.target;
      if (input.files) {
        var reader = new FileReader();
        reader.onload = (e) => {
          this.preview = e.target.result;
        }
        this.image=input.files[0];
        reader.readAsDataURL(input.files[0]);
      }
    }
  }
});

3. 顯示預覽
previewImage 儲存的結果顯示出來
<template v-if="preview">
  <img :src="preview" />
  <p>file name: {{ image.name }}</p>
  <p>size: {{ image.size/1024 }}KB</p>
</template>

執行上面的程式碼就可以達到預覽的效果,最後只要透過API將 image 這個物件透過API丟到後台,就能完成檔案的上傳。(用formData檔案上傳)


更進階的部分,只要在 input tag中加上multiple 就能一次選擇多個檔案。
<input type="file" accept="image/*" multiple>
除了預覽單一檔案,也能支援選擇多個檔案並預覽,有需要的話完整的原始碼已經放到codepen上,歡迎參考。



[Vue.js] 2.6版本開始使用v-slot取代slot


升級到2.6.0+後,slot的功能有一些更動,以v-slot取代slot,雖然在2.0+仍然可以使用,但確定會在Vue3之後捨棄這個用法,既然如此遲早要學會使用v-slot。

基礎使用

<!-- NavLink.vue -->
<a href="#">
  <slot>Link</slot>
</a>

你可以在模板裡面使用:
<nav-link></nav-link>
會使用 <slot> 內的內容當作預設值顯示。

如果你想要客製內容:
<nav-link>Home</nav-link>
則會取代原本的內容,得到這樣的結果。
<a href="#">
  Home
</a>

未指定 name 這個屬性的情況下,預設的名稱都是 default


即將淘汰的slot

1. 命名方式
<!-- NavLink.vue -->
<a href="#">
  <slot name="main">Link</slot>
</a>

2. 使用方式
可以用在任意的元件上,寫入內容:
<nav-link>
  <template slot="main">Home</template>
</nav-link>

<nav-link>
  <h1 slot="main">Home</h1>
</nav-link>


v-slot特性介紹

1. 命名方式
方式不變

2. 使用方式
僅能在 <template> 元件上,寫入內容:
<nav-link>
  <template v-slot:main>Home</template>
</nav-link>

3. 可以縮寫
就像是 v-on: 縮寫成 @v-bind: 縮寫成 :v-slot: 可以簡寫成 #,舉例來說,v-slot:header 等同於 #header
<nav-link>
  <template #main>Home</template>
</nav-link>

除了不能在任意元件是使用 slot ,基本上更動不大,而且更方便好用。不只是 slot 即將走入歷史, slot-scope 是另一個即將被淘汰的用法,想了解更多資訊可以參考官方文件:
https://vuejs.org/v2/guide/components-slots.html

2019年5月16日 星期四

[Vue.js] 升級vue-cli3建立Vue專案


之前安裝vue-cli2來建立vue專案 --『[Vue.js] 在ubuntu安裝 Vue.js』,有需要可以參考。

開始安裝vue-cli 3
npm install --global @vue/cli

完成安裝後可以用指令檢查版本
vue --version
// or
vue -V

vue cli3多了一個 create 功能來建專案:
vue create [APP_NAME]
你可以使用預設選項或根據需求選擇,結束後會自動幫你執行 npm install

整個專案目錄精簡了不少,少了 config/build/ 兩個目錄,然後 static/ 改為 public/
執行下面指令就可以進入開發囉
npm run serve
vue cli不再使用dev當作開發環境的指令,如果你習慣用dev,可以到 package.json 修改script。



因為移除了 config/ ,因此『[Vue.js] 開發用和正式環境的參數設定方式(.env)』的方式勢必就要調整了。
在專案的根目錄新增兩個檔案 .env.env.production,分別用在開發和正式環境。所有的參數名稱必須加上前綴 VUE_APP_ ,只有名稱 VUE_APP_* 的參數,才能在vue專案裡使用。
1. .env
VUE_APP_NODE_ENV: '"development"'
VUE_APP_ROOT_API: '"http://localhost/api"'
2. .env.production
VUE_APP_NODE_ENV: '"production"'
VUE_APP_ROOT_API: '"https://example.com/api"'

使用方式和之前相同
export default {
  mounted() {
    console.log(process.env.VUE_APP_ROOT_API);
  }
}

關於環境變量,參考下面連結了解更多。
https://cli.vuejs.org/guide/mode-and-env.html#environment-variables


2019年4月30日 星期二

[css/scss] 語法整理如何區別CSS、SCSS和Sass


變數(Variables)

SCSS
$font-stack:    Helvetica, sans-serif;
$primary-color: #333;

body {
  font: 100% $font-stack;
  color: $primary-color;
}
Sass
$font-stack:    Helvetica, sans-serif
$primary-color: #333

body
  font: 100% $font-stack
  color: $primary-color
CSS
body {
  font: 100% Helvetica, sans-serif;
  color: #333;
}


巢狀(Nesting)

SCSS
nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  li { display: inline-block; }

  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}
Sass
nav
  ul
    margin: 0
    padding: 0
    list-style: none

  li
    display: inline-block

  a
    display: block
    padding: 6px 12px
    text-decoration: none
CSS
nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
nav li {
  display: inline-block;
}
nav a {
  display: block;
  padding: 6px 12px;
  text-decoration: none;
}


Mixin

SCSS
@mixin transform($property) {
  -webkit-transform: $property;
  -ms-transform: $property;
  transform: $property;
}
.box { @include transform(rotate(30deg)); }
Sass
=transform($property)
  -webkit-transform: $property
  -ms-transform: $property
  transform: $property
.box
  +transform(rotate(30deg))
CSS
.box {
  -webkit-transform: rotate(30deg);
  -ms-transform: rotate(30deg);
  transform: rotate(30deg);
}


Extend/Inheritance

SCSS
/* This CSS will print because %message-shared is extended. */
%message-shared {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

// This CSS won't print because %equal-heights is never extended.
%equal-heights {
  display: flex;
  flex-wrap: wrap;
}

.message {
  @extend %message-shared;
}

.success {
  @extend %message-shared;
  border-color: green;
}

.error {
  @extend %message-shared;
  border-color: red;
}

.warning {
  @extend %message-shared;
  border-color: yellow;
}
Sass
/* This CSS will print because %message-shared is extended. */
%message-shared
  border: 1px solid #ccc
  padding: 10px
  color: #333


// This CSS won't print because %equal-heights is never extended.
%equal-heights
  display: flex
  flex-wrap: wrap


.message
  @extend %message-shared


.success
  @extend %message-shared
  border-color: green


.error
  @extend %message-shared
  border-color: red


.warning
  @extend %message-shared
  border-color: yellow
CSS
/* This CSS will print because %message-shared is extended. */
.message, .success, .error, .warning {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

.success {
  border-color: green;
}

.error {
  border-color: red;
}

.warning {
  border-color: yellow;
}


運算(Operators)

SCSS
.container {
  width: 100%;
}

article[role="main"] {
  float: left;
  width: 600px / 960px * 100%;
}

aside[role="complementary"] {
  float: right;
  width: 300px / 960px * 100%;
}
Sass
.container
  width: 100%


article[role="main"]
  float: left
  width: 600px / 960px * 100%


aside[role="complementary"]
  float: right
  width: 300px / 960px * 100%
CSS
.container {
  width: 100%;
}

article[role="main"] {
  float: left;
  width: 62.5%;
}

aside[role="complementary"] {
  float: right;
  width: 31.25%;
}

參考資料:
https://sass-lang.com/guide

2019年4月23日 星期二

[Symfony] 如何將專案部屬到Heroku(apache/nginx)


之前寫過一篇『[django] 將Django專案部署到Heroku』,可以參考安裝heroku[官網說明]的方式,『[Symfony] ubuntu18安裝symfony 4.2教學』則可以了解composer和symfony的安裝方式,這篇就不再重複說明。

步驟一、新增Symfony專案
首先新增一個專案 symfony_heroku ,也可以根據需求指定專案版本,目前版本是3.4。
composer create-project symfony/framework-standard-edition symfony_heroku/

# 指定Symfony版本 3.0
composer create-project symfony/framework-standard-edition:^3.0 symfony_heroku/


步驟二、新增Procfile
進到專案目錄裡symfony_heroku/,以apache為例,新增檔案Procfile做為執行網站的依據。
cd symfony_heroku
echo 'web: $(composer config bin-dir)/heroku-php-apache2 web/' > Procfile

相當於:
vim Procfile
檔案Procfile內容:
web: $(composer config bin-dir)/heroku-php-apache2 web/
參數$(composer config bin-dir)是考慮到版本差異,可以動態的指到正確的路徑。


步驟三、部屬專案
使用git將所有檔案加入追蹤並commit。
git init
git add .
git commit -m "initial commit"

輸入指令 heroku login 登入後,建立一個Heroku專案,新增相關設定。
heroku create
heroku config:set SYMFONY_ENV=prod
上傳Heroku
git push heroku master


可以打開瀏覽器就可以看到symfony預設的網頁。
https://[APP_NAME].herokuapp.com

查詢Heroku專案名稱(顯示網址)
heroku open
關閉網頁伺服器
heroku ps:scale web=0
開啟網頁伺服器
heroku ps:scale web=1



[nginx]
假設你希望使用nginx作為網頁伺服器,請將Procfile改成:
web: $(composer config bin-dir)/heroku-php-nginx web/
預設/,沒有任何頁面,請看/app.php確定有沒有建立成功。
https://[APP_NAME].herokuapp.com/app.php


參考資料:
https://devcenter.heroku.com/articles/getting-started-with-symfony
https://devcenter.heroku.com/articles/getting-started-with-php

相關文章:

2019年4月21日 星期日

[Github] 在github.io建立免費的網站


Github提供免費的方式建立自己的網站,不過只限於靜態的網頁,適合寫個人履歷、部落格或網站作品的分享,設定方法如下:

步驟一、建立Repository
名稱為[USERNAME].github.io[USERNAME]請填入github的帳戶名稱。



步驟二、新增頁面
用command line方式進行說明
git clone https://github.com/[USERNAME]/[USERNAME].github.io
cd [USERNAME].github.io
vim index.html

內容可以自己決定
<!DOCTYPE html>
<html>
 <head>
  <title>Home</title>
 </head>
 
 <body>
  <h1>Welcome</h1>
  <p>This is my first page.</p>
 </body>
</html>


步驟三、更新
git add index.html
git commit -m "Initial commit"
git push -u origin master

最後打開瀏覽器就可以看到成果囉!
https://[USERNAME].github.io



除了[USERNAME].github.io以外Repository,其他也可以建立一個新的分支(branch),命名為gh-pages,透過github.io訪問這個專案,同時保持所有的專案維護的方便性。
https://[USERNAME].github.io/[REPO_NAME]


https://pages.github.com/

2019年3月28日 星期四

[GCP] 讓Google Vision API幫你做ORC文字辨識(Python實例)


一、GCP設定
啟用方式
打開連結,點選『啟用』。

每個月有提供免費額度(詳細請看google的說明),每月使用的前 1,000 個單位免費,如果怕被收費的話記得關閉這個API。

關閉的方法
(1) 點選『管理』

(2) 點選『停用API』


二、安裝套件
(1) 安裝vision
pip3 install google-cloud-vision

(2) 安裝sdk
方法一、
pip3 install google-cloud-sdk
方法二、
sudo apt install snapd
sudo snap install google-cloud-sdk --classic


三、使用方式
(1) 輸入指令,獲得授權。
gcloud auth application-default login
點選出現的連結(用瀏覽器打開),選擇你的google帳戶登入。
登入google後,把出現的驗證碼回填。
記得!伺服器重開之後都要再重新登入。

(2) 新建檔案,執行程式。
# detect.py
import sys
import io
from google.cloud import vision

def detect_text_uri(uri):
    client = vision.ImageAnnotatorClient()
    image = vision.types.Image()
    image.source.image_uri = uri

    response = client.text_detection(image=image)
    texts = response.text_annotations
    print('Texts:')
    print(texts[0].description)

if __name__ == '__main__':
    detect_text_uri(sys.argv[1])
執行程式碼
python3 detect.py IMAGE_URL

[實測結果]
python3 detect.py https://www.eastcoast-nsa.gov.tw/image/6921/1024x768
圖片來源:https://www.eastcoast-nsa.gov.tw/image/6921/1024x768

輸出文字:


Google Vision Api Example
Vision API支援很多國的語言,辨識度也相當不錯!Google有針對Vision API提供很多範例的程式碼,你可以[下載]程式碼來玩玩看。
document_text 標示圖片中的文字 [連結]
python3 doctext.py resources/text_menu.jpg  -out_file result.png
將圖片中的文字標示出來。

解析圖片的內容、計算評分 web [連結]
python3 web_detect.py https://picsum.photos/400?image=111
除了解析圖片的內容,還會列出使用這個圖片的網站、與此圖片相同的圖片(不同路徑)、部分相同的圖片等等。


參考資料:
https://cloud.google.com/vision/overview/docs/
https://cloud.google.com/vision/docs/quickstart-client-libraries#client-libraries-install-python