NOTE: 
✔
NOTE: 
✔ Khi làm việc với props, việc không tin bất cứ data nào truyền vào từ parent là có cơ sở (lỗi API, lỗi logic bên BE, lỗi name file,...) 
✔ Việc cover các case xuất hiện với data truyền vào là thực sự cần thiết đối với việc handing errors on production 
✔ Sử dụng các mệnh đề có sẵn: type required default validator 
NOTE: 
✔ Xảy ra trường hợp, data điền đúng kiểu nhưng bị sai value (sai định dạng, sai kiểu value,...) 
✔ Validator dữ liệu có thể gây dài dòng, nhưng sẽ là tuyệt với nếu bạn handling errors ngay từ lúc khởi điểm 
Extend: 
✔ Vậy chúng ta có thể tạo 1 helper (custom hook) cho việc validator này đúng không nhỉ 
✔ Error mặc định của Vue thường không rõ ràng, chúng ta có thể customize lại message 
✔ Ex: validator type of images (allow *.jpg || *.png) 
// file component want to use helper
import { validatorImageType } from '../helpers/validatorImageType.js'
// file helper validatorImageType
const validatorImageType = (propString) => {
  const hasImagesDirectory = propString.indexOf('/images/') > -1
  const isPNG = prop.endWith('.png')
  const isJPG = prop.endWith('.jpeg') || prop.endWith('.jpg')
  return hasImagesDirectory && isPNG && isJPG
}
export default validatorImageType
SANBOX CODE: building-controlled-components
NOTE: 
✔ Bản chất của v-model là việc lắng nghe input(:modelValue="pageTitle") và emit dữ liệu(@update:modelValue="pageTitle = $event") 
✔ v-model chỉ nên dùng cho input và component => không nên sử dụng cho các thành phần khác 
✔ Sử dụng emit và gộp các emit 
✔ form tối ưu bằng cách loại bỏ real-time sync in input @input.once => chỉ check khi click submit 
✔ Sử dụng Object.fromEntries(new FormData(event.target)) thay cho v-model nếu dùng với form nhiều thành phần 
✔ Khi emitdata, chỉ sử dụng từ update:someModelValue khi thao tác với v-model => với các component thì bỏ từ update để tránh gây hiểu nhầm 
- 
Khi thao tác với
form elements(input, checkbox, select,..) hãy linh động trong việc xử lý các$emitvàprops=> hãy tách cácform elementsthành các components riêng lẻ và để trongglobal components / common components

 - 
Hãy sử dụng
emit gộpcủa Vue3 để việc control được hiệu quả hơn 
https://v3.vuejs.org/guide/component-custom-events.html#event-names
===================
// Emit with Vue3: setup(...)
emits: ['your-event', 'handle-confirm-del-data'],
setup(props, { emit }) { 
  ...
  emit('your-event', dataWantToEmit); // Hoặc sử dụng context(attrs, slots, emit) cho nó ngắn: setup(props, context) {} || context.emit('yourEvent', dataWantToEmit);
  emit("handle-confirm-del-data", false); //data to close del dialog
}
<your-child @your-event="onYourEvent" />
onYourEvent(dataWantToEmit) {
  ...
}
// Gộp các emit trong 1 setup
<ChildComponent v-model="pageTitle" />
export default {
  props: {
    modelValue: String // previously was `value: String`
  },
  emits: ['update:modelValue'],
  methods: {
    changePageTitle(title) {
      this.$emit('update:modelValue', title) // previously was `this.$emit('input', title)`
    }
  }
}
Vue 3 thay đổi syntax của v-model:
<ChildComponent v-model="pageTitle" />
<!-- shorthand for: -->
<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>
v-modelcó thể sử dụng với các element không thuộc hệ thốngform elements=> với điều kiện chỉ có 1 element duy nhất có trongcomponent
// Normal with form elements
<input @input="email = $event.target.value">
// Special with element not belong to form
<some-component :modelValue="newLetter" @update:modelValue="(newValue) => { newLetter = newValue }"></some-component>
//Child component
setup(props, { emit }) {
   ...
   emit('your-event', dataWantToEmit);
}
//Parent component
<your-child @your-event="onYourEvent" />
onYourEvent(dataWantToEmit) {
  console.log(dataWantToEmit)
}
- Việc lắng nghe liên tục mọi 
keydown||clickedkhiend-userthao tác trên form chỉ thực sự đúng đắn khi làm việc vớireactive form(form muốn response trực tiếp hành động của user) => hãy tối giản bằng việc khisubmit mới lắng nghe=> giải phóng được 1 phần bộ nhớ và giảm tình trạng lag nếu làm với super form. 
@input.once
SANBOX CODE: customizing-controlled-component-bindings
NOTE: 
✔ v-model default sẽ không được tự nhiên vì sử dụng modelValue & update:modelValue => có thể custom bằng các value cụ thể 
//Parent
<Custom-component v-model:message="message" />
//Child
<input type="text" :value="message" @input='emit("update:message", $event.target.value);'>
SANBOX CODE: wrapping-external-libraries-as-vue-components
NOTE: 
✔ Hãy chú ý sử dụng các method có sẵn của lib để custom lại các event 
- Đôi khi, việc thêm 1 thư viện ngoài(datePicker) vào để gọi trong input gây phiền toái nhất định: $event chọn ngày không cập nhật với biến ref thông thường. Vì chúng không được tạo ra để sync với biến đó => hãy sử dụng các custom event của lib (onSelect()) để xác định event click day. 
 - https://github.com/b0yblake/Vue3-Form-Best-Practice/blob/main/src/components/common/form/DatepickerPikaday.vue
 
SANBOX CODE: encapsulating-external-behavior-closing-on-escape
NOTE: 
✔ Web accessibility luôn luôn đặt lên hàng đầu mỗi khi thao tác với dialog 
✔ Dialog khi bật lên cần được kiểm soát cả ở phần keyboard: close = esc, enter, blankspace 
✔ Hành vi người dùng cần được chú trọng khi họ dùng keyboard 
✔ Đối với những DOM sinh ra sau lifecycle:created, nếu muốn control được, nên sử dụng nextTick hoặc sử dụng vanila js tại thời điểm click 
✔ Hãy linh động trong việc sử dụng js, chú ý đến việc tối ưu hiệu suất (dùng js tối ưu được hiệu suất ngay tại component do không phải v-model 2way-binding) 
Way1: sử dụng keydown = esc button, enable tabindex để có thể focus được 
@keydown.esc="handleEsc" tabindex="0" ref="dialog"
Way2: sử dụng vanila js nhằm bắt sự kiện click tại thời điểm dialog đã sinh ra (không cần care về việc sinh ra hay sau dom update)
=======================
methods: {
  createClickEvent: function() {
    let self = this;
    document
      .querySelector(".util_per_pay_rate .btn_open_dialog")
      .addEventListener("click", function() {
        self.dialog = true;
      });
  }
},
mounted() {
  this.createClickEvent();
}
========================
created() {
  document.addEventListener('keydown', (e) => {
    if(e.key === 'Escape' && this.show) {
      this.createClickEvent();
    }
  })
}
SANBOX CODE: encapsulating-external-behavior-background-scrolling
NOTE: 
✔ Khi bật Dialog vấn đề gặp phải là chúng ta cần remove scroll: hãy chú ý về cách sử dụng bằng class toggle tại body => chúng sẽ hữu hiệu khi chúng ta handle được, còn không => hãy sử dụng vanila js 
✔ Hãy cố gắng cover 1-2 case tiếp theo sau này khi mở rộng app 
watchEffect(() => {
  props.active ? props.preventBackgroundScrolling && document.body.style.setProperty('overflow', 'hidden') : props.preventBackgroundScrolling && document.body.style.setProperty('overflow')
})
watch(() => props.selected, (first, second) => {
  console.log(
    "Watch props.selected function called with args:",
    first,
    second
  );
});
SANBOX CODE: encapsulating-external-behavior-portals
NOTE: 
✔ Với các dialog trên từng component, hãy cứ viết ở trên các components để dễ handle data, sau đó sử dụng teleport kết hợp với slot 
✔ Việc handle data của tất cả các popup ở cùng 1 component trung gian đem lại hiệu quả rõ rệt so với việc handle data tại các component common 
https://github.com/b0yblake/Vue3-Form-Best-Practice/blob/main/src/views/Form.vue 
//index.html
<body>
  <div id="app"></div>
  <!-- Use teleport to move dialog to here -->
  <div id="layer"></div>
</body>
// Component
<teleport to="#layer">
  <some-component-dialog :data="data" @click="some-element" />
</teleport>
SANBOX CODE: encapsulating-external-behavior-reusing-portals
NOTE: 
✔ teleport hiệu quả với multiple => cái nào được move trước sẽ xuất hiện trước 
SANBOX CODE: injecting-content-using-slots
DON'T LET YOURSELF TURN INTO THIS CASE: 
✔ Bạn đinh sloved 1 required đơn giản với nhiều case tại 1 button 
✔ Button đó có các case spin title icon left icon right ,.. 
✔ Hãy đúng đắn suy nghĩ trước khi làm 1 component 
NOTE: 
✔ SLOT là 1 tính năng cực kỳ hay ho cho việc tái sử dụng tối đa số lần components xuất hiện. 
✔ Việc kết hợp cùng với class tại component tag cũng đem lại sự tiện lợi cho việc tái xử dụng component 
// Tái sử dụng với trường hợp đặt các case cố định cho các vùng của header
// Parent
<dialog>
  <template #header>
    <h1>Dialog main</h1>
  </template>
  <template #default>
    <h1>Dialog main</h1>
  </template>
  ...
</dialog>
// Child popup
<template>
  <div class="c-base-popup">
    <div v-if="??" class="c-base-popup__header">
      <slot name="header"></slot>
    </div>
    <div v-if="??" class="c-base-popup__subheader">
      <slot name="subheader"></slot>
    </div>
    <div class="c-base-popup__body">
      //Default slot
      <slot></slot> 
      <h1>{{ title }}</h1>
      <p v-if="description">{{ description }}</p>
    </div>
    <div v-if="??" class="c-base-popup__actions">
      <slot name="actions"></slot>
    </div>
    <div v-if="??" class="c-base-popup__footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>
SANBOX CODE: native-style-buttons-using-slots-and-class-merging
NOTE: 
✔ CLass có thể merging giữa component tag & first-element-in-component 
<!-- Common dialog -->
<BadgeDialog :dataDialog="form" v-model:active="activeDialog" v-show="activeDialog" class="flex">
// BadgeDialog component
<template>
  <div class="nes-dialog abc" id="badge-dialog"></div>
</template>
// Result
<div class="nes-dialog abc flex" id="badge-dialog"></div>
SANBOX CODE: extending-components-using-composition
NOTE: 
✔ Chú ý khi sử dụng compositionAPI => cấu trúc ref & reactive sẽ gây khó khăn 
✔ Việc tái sử dụng component luôn được đặt lên hàng đầu => hãy xem kỹ ví dụ để thấy được hiệu quả khi sử dụng component trung gian 
//Sử dụng Object.assign cho custom hook (composables)
  setup() {
    const initialState = {
      name: "",
      lastName: "",
      email: ""
    };
    const form = reactive({ ...initialState });
    function resetForm() {
      Object.assign(form, initialState);
    }
    function setForm() {
      Object.assign(form, {
        name: "John",
        lastName: "Doe",
        email: "john@doe.com"
      });
    }
    return { form, setForm, resetForm };
  }
SANBOX CODE: passing-data-up-using-scoped-slots
NOTE: 
✔ Khi ta sử dụng data tại Child (vì nhiều lý do), mà parent là nơi call component tag: 
✔ NEW way: props đôi khi có thể truyền dưới dạng function (thay vì data như trước) => Chỉ là cách tham khảo, ít người thích dùng kiểu này vì rườm ra và k flexable 
✔ HIGH RECOMMEND: Sử dụng slot như 1 dạng flexiable code, để layout có thể tùy chỉnh theo parent mà vẫn sử dụng data tại child 
======= Sử dụng data tại child như props ===============
// Parent
<contact-list :pseudo-slot="({ contact }) => contact.name.first"></contact-list>
// Child
<div class="child">
  {{ pseudoSlot({ contact: contact }) }}
</div>
======= Sử dụng data tại child => passing data ngược lại parent thông qua slot ===============
// Parent
// Có thể custom layout như này
<contact-list>
  <a slot-scope="{ contact }" :href="`/contacts/${contact.id}`">
    {{ contact.name.first }}
  </a>
</contact-list>
// Hoặc như này
<contact-list>
  <div slot-scope="{ contact }">
    <strong class="user-title">{{ contact.name.first }}</strong>
  </div>
</contact-list>
// Child
<div class="child">
  <slot :contact="contact"></slot>
</div>
SANBOX CODE: render-functions-101
NOTE: CHÚNG TA CẦN HIỂU ĐỂ SỬ DỤNG 1 CÁCH HIỆU QUẢ 
✔ Using full power of JS (Sử dụng tất cả sức mạnh của JS)
✔ Dynamically creating HTML tags (Tự động tạo các tag HTML)
✔ Good for library creators (Sẽ hiệu quả nếu dùng với các thư viện tự động)
❌ Sẽ phức tạp hơn khi lạm dụng vì 1 số code không cần thiết (html tĩnh, passing only data, ...)
❌ Lỗi sinh ra trong im lặng (silently failed)
❌ Gây lú lẫn vì nhiều syntax
❌ Lồng vào nhau nhiều thứ chứ không tách bạc như template
🦟 Fix: hãy chia nhỏ các thành phần và sử lý từng phần 1
//Parent
<RenderFuncEx heading="'1'" />
//Child
<script>
import {
  h,
} from 'vue';
export default {
  props: {
    heading: {
      type: Number,
      required: true,
      default: 2,
      validator: propValue => {
        const isNumber = isNumber(propValue)
        return isNumber && false
      }
    }
  },
  setup(props, { context }) {
    return () => h(
      `h${props.heading}`,
      {
        class: 'text-lg title',
        style: 'color: red',
      },
      'Simple Form Example'
    )
  }
}
</script>
NOTE:
✔ return () => h(element, attributes, children) 
✔ Có thể sử dụng được tính reactivity của compositionAPI 
✔ Multiple render function là có cơ sở, hãy làm tuần tự 
setup(props, { context }) {
  const count = ref(0);
  const increament = () => {
    return count.value++
  }
  return () => h(
    `h${props.heading}`,
    {
      class: 'text-lg title',
      style: 'color: red',
      onClick: increament
    },
    [
      'Simple Form Example',
      h(
        `h${props.heading + 1}`,
        {
          style: 'color: green',
        },
        count.value
      )
    ]
  )
}
SANBOX CODE: render-functions-and-components
NOTE: 
✔ v-model không thể dùng trong render-function => hãy dùng các cú pháp của render-func: https://v3.vuejs.org/guide/render-function.html#v-model 
✔ https://v3.vuejs.org/guide/render-function.html 
<script>
import {
  h,
} from 'vue';
import TextButton from '@/components/common/button/TextButton.vue';
export default {
  name: "RenderFuncEx",
  components: {
    TextButton,
  },
  setup(props, { context }) {
    //<TextButton :title="'PressMe'" />
    return () => h(
      TextButton,
      {
        title: 'Simple Form Example',
        onClick: () => alert('clicked')
      }
      
    )
  }
}
</script>
SANBOX CODE: render-functions-and-children 
SANBOX CODE: render-functions-and-slots 
NOTE: 
✔ Vậy thì với các vòng lặp đơn giản (v-if v-for v-show ...) 
NOTE: 
✔ Đôi khi, việc sử dụng render function là 1 điều quen thuộc, việc lặp đi lặp lại code sẽ dẫn tới sự nhàm chán, hãy thử factory render function
✔ Giải quyết được vấn đề về việc lặp code và sử dụng được pattern
// Normal way: in file ProductListing.vue
<template>
  <ListingContainer
    :service="productService"
  />
</template>
<script>
import productService from '../services/product';
import ListingContainer from './ListingContainer';
export default {
  name: 'ProductListing',
  components: {
    ListingContainer,
  },
  created() {
    this.productService = productService;
  },
};
</script>
// Use factory way: in file ProductListing.vue
<script>
import containerFactory from './factories/container';
import productService from '../services/product';
import ListingContainer from './ListingContainer';
export default containerFactory(ListingContainer, {
  service: productService
});
</script>
// In file factories/container.js
export default (Component, props) => ({
  functional: true,
  props: Component.props,
  render(h, context) {
    return h(Component, {
      props: { ...context.props, ...props },
    });
  }
});
SANBOX CODE: data-provider-components
SANBOX CODE: getting-started-with-renderless-ui-components 
SANBOX CODE: passing-data-props-from-renderless-components 
SANBOX CODE: passing-action-props-from-renderless-components 
SANBOX CODE: passing-binding-props-from-renderless-components 
SANBOX CODE: renderless-ui-components-functions-as-binding-props 
SANBOX CODE: implementing-alternate-layouts-with-renderless-components 
SANBOX CODE: configuring-renderless-components 
SANBOX CODE: wrapping-renderless-components 
NOTE: 
✔
✔
✔
✔
✔
✔
SANBOX CODE: element-queries-as-a-data-provider-component
SANBOX CODE: building-compound-components-with-provide-inject
NOTE: 
✔ https://github.com/wnr/element-resize-detector 
SANBOX CODE: building-a-compound-sortable-list-component
NOTE: 
✔ https://github.com/SortableJS/Vue.Draggable
SANBOX CODE: building-a-search-select-data-bindings 
SANBOX CODE: building-a-search-select-filtering 
SANBOX CODE: building-a-search-select-focus-management 
SANBOX CODE: building-a-search-select-making-it-controlled 
SANBOX CODE: building-a-search-select-keyboard-navigation 
SANBOX CODE: building-a-search-select-click-outside-component 
SANBOX CODE: building-a-search-select-integrating-popperjs 
NOTE: 
✔
✔
✔
✔
✔











