ASTRO Camp 7th
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 0428 購物車實作練習 ###### tags: `JavaScript` ## 環境設定 - [龍哥做的貓貓購物車架構 GitHub 連結](https://github.com/kaochenlong/shopping-cart-v2) - 拿到新專案時第一件事:看 `README` - make sure yarn is installed. - run yarn install to install all packages. - run yarn run dev or yarn dev to run a development server on your local machine. - and write all implements in src/main.js. - 給用 ubuntu 的朋友們: - 如果遇到 node 版本太舊的問題可以參考下面網站 - [NodeSource Node.js Binary Distributions](https://github.com/nodesource/distributions/blob/master/README.md) ## 讓每個按鈕都可以動 ```javascript= document.addEventListener('DOMContentLoaded', () => { const buttons = document.querySelectorAll('.card .btn') buttons.forEach(btn => { btn.addEventListener('click', () => { console.log('hi'); }) }) }) ``` ## 把功能拉出來 - 把固定使用的 function 抓到外面 ```javascript= const addToCart = btn => { btn.addEventListener('click', () => { console.log('hi'); }) } document.addEventListener('DOMContentLoaded', () => { const buttons = document.querySelectorAll('.card .btn') buttons.forEach(btn => addToCart(btn)) // buttons.forEach(addToCart) 這兩行道理一樣 }) ``` ## 修改 addToCart 功能 ### 抓取商品名跟價格 - 觀察 HTML 架構設定 ```javascript= const addToCart = btn => { btn.addEventListener('click', (e) => { const card = e.currentTarget.parentElement.parentElement const title = card.querySelector('.card-title').textContent const price = parseFloat(card.querySelector('.price') .textContent .replace('$', '')) }) } ``` ### 相關運算式 #### e.target & e.currentTarget - [Event.target](https://developer.mozilla.org/zh-TW/docs/Web/API/Event/target) - [Event.currentTarget](https://developer.mozilla.org/zh-TW/docs/Web/API/Event/currentTarget) - e.target 是指向按到的物件 - e.currentTarget 是指向你裝 eventListener 的物件 #### replace - [String.prototype.replace()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/String/replace) - 預設使用 const 宣告,若值會有變化使用 let ```=javascript const price = card.querySelector('.price').textContent.replace('$', '') //將 $ 取代成 '' ``` #### parseFloat 轉成小數點 - [parseFloat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat) - 把一個東西換成小數點 #### parseInt 轉成整數 - [parseInt](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/parseInt) ### constructor 建構子 - [constructor](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Classes/constructor) - 隨著 class 一同建立並初始化物件的特殊方法;一個 class 只能有一個 ```javascript= class Monster { } class Hero { constructor(name) { this.name = name } } const h1 = new Hero('悟空') console.log(h1) // Hero {name: "悟空"} ``` ```javascript= class Monster { constructor(hp) { this.hp = hp } } class Hero { constructor(name) { this.name = name } hit(monster) { if (monster.hp > 0) { console.log('hit!!!') moster.hp -= 10 } else { console.log('booooom!') } } } const h1 = new Hero('悟空') const m1 = new Monster(50) hi.hit(m1) // 先偵測 hp 然後再對 m1 扣 hp ``` 物件導向程式設計 把邏輯包在物件裡面,讓物件變成彼此的參數傳來傳去 現階段可以將constructor 跟 initialize 看作一樣的功能 但實際還是有點不一樣 ## 把一些建構式拆出來 - 在 scripts 中建立新檔案 `cart.js` `cart-item.js` cart.js ```javascript= class Cart { } export default Cart ``` cart-item.js ```javascript= class CartItem { } export default CartItem ``` main.js ```javascript= import Cart from './cart' import CartItem from './cart-item' ``` ## 建構購買物品 /cart-item.js ```javascript= class CartItem { constructor(title, price, quantity = 1) { this.title = title this.price = price this.quantity = quantity } } ``` /main.js ```javascript= const addToCart = btn => { btn.addEventListener('click', (e) => { const card = e.currentTarget.parentElement.parentElement const title = card.querySelector('.card-title').textContent const price = parseFloat(card.querySelector('.price') .textContent .replace('$', '')) // 加到購物車 const item = new CartItem(title, price) console.log(item) // 檢查一下 }) } ``` - 解構 - 因為物件本身沒有順序問題 - 所以直接以名字去指定 - 上一個作法會有順序問題,如果打反了就會出問題 /cart-item.js ```javascript= class CartItem { constructor({ title, price, quantity = 1 }) { this.title = title this.price = price this.quantity = quantity } } ``` /main.js ```javascript= const addToCart = btn => { btn.addEventListener('click', (e) => { const card = e.currentTarget.parentElement.parentElement const title = card.querySelector('.card-title').textContent const price = parseFloat(card.querySelector('.price') .textContent .replace('$', '')) // 加到購物車 const item = new CartItem({ title, price }) // const item = new CartItem({title, price}){ // title = title // price = price // } // 如果 key 跟 value 是一樣的,可以只寫一個 console.log(item) }) } ``` ## 建構購物車 /cart.js ```javascript= class Cart { constructor() { this.items = [] } // constructor(items = []) { // this.items = items // } add(item) { this.items.push(item) // this.items為空陣列,將 item 塞進去 console.log(this.items); } } export default Cart // 預設匯出Cart ``` /main.js - 可以不用放在 DOMContentLoaded 裡面 ```javascript= const cart = new Cart() ``` ## 解決同一商品重複選取問題 - 去抓 id 然後檢查購物車裡面是不是有相同的 id - 有相同的 => 增加數量 - 沒有相同的 => 增加品項,數量預設 1 /cart.js ```javascript= add(item) { const foundItem = this.items.find(t => t.id == item.id) // 檢查有沒有重複 if (foundItem) { // 增加數量 foundItem.increment(n) } else { this.items.push(item) // 增加品項,數量預設 1 } } ``` /main.js ```javascript= const addToCart = btn => { btn.addEventListener('click', (e) => { const card = e.currentTarget.parentElement.parentElement const title = card.querySelector('.card-title').textContent const price = parseFloat(card.querySelector('.price') .textContent .replace('$', '')) const id = card.dataset['productId']} // 加到購物車 const item = new CartItem({ id, title, price }) ``` /cart-item.js ```javascript= class CartItem { constructor({id, title, price, quantity = 1}) { this.title = title this.price = price this.quantity = quantity this.id = id } increment(n = 1){ this.quantity += n } } ``` ## 渲染購物車內容 - 去看 html 檔案,分析 table 裡的架構 - 建立一個 ui 的檔案 /ui.js ```javascript= //接到 回傳 const buildItemList = cart =>{ return ` <tr> <td>老大</td> <td><input type="number" class="quantity" value="1"></td> <td>$20</td> <td>$20</td> <td><button class="remove-item-btn btn btn-danger btn-sm"><i class="fas fa-trash-alt"></i></button></td> </tr> ` } export { buildItemList } ``` /main.js ```javascript= import { buildItemList } from './ui' const addToCart = btn => { btn.addEventListener('click', (e) => { const card = e.currentTarget.parentElement.parentElement const title = card.querySelector('.card-title').textContent const price = parseFloat(card.querySelector('.price') .textContent .replace('$', '')) const id = card.dataset['productId'] // 加到購物車 const item = new CartItem({ id, title, price }) cart.add(item) const result = buildItemList(cart) document.querySelector('.cart tbody').innerHTML = result }) } ``` - 把功能拉到外面 /main.js ```javascript= const renderUI = () => { const result = buildItemList(cart) document.querySelector('.cart tbody').innerHTML = result } const addToCart = btn => { btn.addEventListener('click', (e) => { const card = e.currentTarget.parentElement.parentElement const title = card.querySelector('.card-title').textContent const price = parseFloat(card.querySelector('.price') .textContent .replace('$', '')) const id = card.dataset['productId'] // 加到購物車 const item = new CartItem({ id, title, price }) cart.add(item) renderUI() }) } ``` - 讓購物車選到不同商品時會有不同品項 /ui.js ```javascript= const buildItemList = cart =>{ const r = cart.items.map(item => { return ` <tr> <td>${item.title}</td> <td><input type="number" class="quantity" value="1"></td> <td>$${item.price}</td> <td>$20</td> <td><button class="remove-item-btn btn btn-danger btn-sm"><i class="fas fa-trash-alt"></i></button></td> </tr> ` }) return r.join('') // 因為 map 出來是一個陣列,他直接渲染中間會有一個逗號 } ``` ## 清空購物車 /main.js ```javascript= document.addEventListener('DOMContentLoaded', () => { const buttons = document.querySelectorAll('.card .btn') buttons.forEach(addToCart) document.querySelector('.empty-cart').addEventListener('click', () => { cart.empty() // 把購物車清空 renderUI() // 把購物車畫面重新渲染 }) }) ``` /cart.js ```javascript= class Cart { empty() { this.items = [] // 把購物車清空的功能做出來 } } ``` ## 計算小計 - 額外加功能的好處 - 增加可讀性 - 降低維護難度 /cart-item.js ```javascript= class CartItem { totalPrice() { return this.price * this.quantity }} ``` /ui.js (把小計換掉) ```javascript= const buildItemList = (cart) => { const list = cart.items.map(item => { return `<tr> <td>${item.title}</td> <td><input type="number" class="quantity" value="${item.quantity}"></td> <td>$${item.price}</td> <td>$${item.totalPrice()}</td> <td><button class="remove-item-btn btn btn-danger btn-sm"><i class="fas fa-trash-alt"></i></button></td> </tr>` }) return list.join('') } ``` ## 計算總計 - 把每一個品項的小計都加起來 /cart.js ```javascript= class Cart { totalPrice() { let total = 0 this.items.forEach(item => { total += item.totalPrice() }) return total } } ``` - 比較高級的做法 - [reduce 用法的 MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) ```javascript= class Cart { totalPrice() { return Math.round(this.items.reduce( (total, currentItem) => total + currentItem.totalPrice(), 0 )* 100) / 100 } // 如果品項多的話會有浮點數計算問題,所以加四捨五入功能避免 ``` - 把結果渲染上去 /main.js ```javascript= const renderUI = () => { const result = buildItemList(cart) document.querySelector('.cart tbody').innerHTML = result document.querySelector('.cart .total-price').textContent = '$' + cart.totalPrice() } ``` ## 一進網頁就清空購物車 - 不動 HTML 的話可以直接 RenderUI() - 其實應該直接把 HTML 不需要的部分刪掉 - 檢視原始碼的話還是會在 ## 刪除購物車內品項 - 偷塞 id 進去做出來的 innerHTML /ui.js ```javascript= const buildItemList = (cart) => { const list = cart.items.map(item => { return `<tr> <td>${item.title}</td> <td><input type="number" class="quantity" value="${item.quantity}"></td> <td>$${item.price}</td> <td>$${item.totalPrice()}</td> <td><button data-id="${item.id}" class="remove-item-btn btn btn-danger btn-sm"><i class="fas fa-trash-alt"></i></button></td> </tr>` }) return list.join('') } ``` - 直接在渲染出東西的時候裝監聽器 /main.js ```javascript= const renderUI = () => { document.querySelectorAll('.remove-item-btn').forEach(btn => { btn.addEventListener('click', (e) => { cart.removeItemId(e.currentTarget.dataset['id']) renderUI() }) }) } ``` - 在購物車裡裝上清除品項的功能 - [filter 的 MDN 說明](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) /cart.js ```javascript= removeItemId(id) { this.items = this.items.filter(item => item.id != id) } // 過濾符合條件的東西(只要不是選到 ID 的品項就留下來 => 刪除被選到的) ``` * filter 在 ruby 中的用法 * [filter api](https://apidock.com/ruby/Array/filter) ```ruby= a = [1, 2, 3, 4, 5] a.filter{|n| n < 2} a.select{|n| n < 2} ``` ## 防止購物車數量小於 0 - 設定最小值 /ui.js ```javascript= const buildItemList = (cart) => { const list = cart.items.map(item => { return `<tr> <td>${item.title}</td> <td><input type="number" min="1" class="quantity" value="${item.quantity}"></td> <td>$${item.price}</td> <td>$${item.totalPrice()}</td> <td><button data-id="${item.id}" class="remove-item-btn btn btn-danger btn-sm"><i class="fas fa-trash-alt"></i></button></td> </tr>` }) return list.join('') } ``` ## 打包 - 使用 yarn 裡面的 parcel 套件 - 到 `package.json` 裡面檢查 `"script"` 裡面的東西 - parcel 裡有建立一個 build 功能可直接使用 - 在 terminal 裡面輸入 `$ yarn build` 就可以打包進 `/dist` 內 - 類似 rails 的 webpack 功能 * scripts 裡面會是一個物件 * Key 就是可以輸入的指令 * Value 就是對到的指令 * 都使用字串 * 有點類似簡寫的概念 ## Vue 起手式 ```javascript= new Vue({ el: document.querySelectoy('#hello') el: '#hello' 上下等同 //綁定物件↑ data: { name: kk } }) <div id='hello'>hello {{ name }}</div> ``` ## 佈署網站 - [netlify](https://www.netlify.com/) - 把 `/dist` 裡面的東西丟上去就佈署完畢 - 一堆 react 的程式碼,丟上去會自動編譯 - 拖拉上去就有作品集或履歷...,還可以有網址 --- ## 題外話 ### webpack & parcel - webpacker: 是一個 gem,用來打包 webpack 功能 - 瀏覽器無法讀取scss檔案 - parcel 幾乎不用設定 自動編譯 - 上線使用 `parcel build` ### 物件解構 常用 - 只要等號右邊是物件,就可以用解構 - [參考資料:解構賦值](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) ```javascript= const obj = {name: 'kk', age: 123} const name = obj.name const age = obj.age // 上面兩行等於下面一行 const { name, age } = obj ``` - 物件所有屬性的預設值是undefined ```javascript= const obj = {name: 'kk', age: 123} const { name, age, hello} = obj console.log(hello) // undefined ``` - 解構賦值 ```javascript= // 方法 1 function a(words){ const {name, age} = words console.log(name) console.log(age) } // 方法 2 function a({ name, age }){ console.log(name) // kk console.log(age) // 18 } // 方法 3 function a({ name, age }){ console.log(name) // kk console.log(age) // 18 } a({ name: 'kk', age: '18' }) ``` ### 其實不用所有 function 都放在 DOMContentLoaded 裡面 通常放裡面只是為了集大成,裝 eventListener... 等等 其他的功能可以先在外面設定, ### includes() - 是否包含 ```javascript= const li = ['a', 'b' , 3, 'c'] console.log(li.includes(4)) // false console.log(li.includes(3)) // true ``` ### 是否有 + 條件式 ```javascript= const li = [1, 2, 3, 4, 8] for(n of li) { if(n >= 5) { console.log(true) break; } } // break ``` ```javascript= const li = [1, 2, 3, 4, 8] const r = li.some(n => n >= 5) // 上下兩行相同,是否有些元素有符合條件 const r = li.some(function(n) {return n>5)) console.log(r) ``` ```javascript= const li = [1, 2, 3, 4, 8] const r = li.every(function(n) {return n > 0}) // 是否全部元素都符合條件 console.log(r) ``` ```javascript= const li = [1, 2, 3, 4, 8] const greaterThanZero = n => n >= 0 const r = li.every(greaterThanZero) console.log(r) ``` ```javascript= const li = [1, 2, 3, 4, 8] const greaterThanZero = n => n >= 0 const r = li.some(greaterThanZero) console.log(r) ``` ## 找陣列裡的物件裡的東西 - 回傳找到的第一筆物件 ```javascript= const list = [ {name: 'kk', age: 18} {name: 'aa', age: 12} {name: 'bb', age: 20} {name: 'cc', age: 30} ] const found = list.find(n=>{ return n.age < 18 }) // const found = list.find(n => n.age < 18) console.log(found) // {name: 'aa', age: 12} ``` - 在 ruby 內的相似方法 ```ruby= list = [ {name: 'kk', age: 18} {name: 'cc', age: 30} ] p list.find{ |n| n[:age] > 18 } ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully