Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.


See the Browser Extension Development in Action column for this series of articles


TagDown is an open source bookmark management plug-in that allows you to view, add, and modify bookmarks using the extension. It also allows you to export bookmarks in different ways.

In addition to the usual bookmark management features, it also has the following features:

  • Support 🎬newBookmark and attach additional information, for exampletags,groups ç­‰
  • Support 🎬exportAny bookmark isjsonThe document
  • Browse the hierarchical bookmark data as a 🎬 tree
  • Open multiple bookmarks in one click. You can open bookmarks in 🎬 TAB group

Pop-up page development

In the development of pop-up pages, the main problem to be solved is the data transfer between Vue components.

In order to facilitate the reuse and maintenance of the code, the page is divided into multiple Vue components. For example, in the browsing state of the pop-up page, BrowserPage, the parent component of the web page, has four major sub-components:

  • BrowserHeader.vue
  • BrowserMenu.vue å’Œ BrowserMenuPin.vue
  • BrowserGrid.vue å’Œ BrowserTree.vue, as well asBrowserPin.vue
  • BrowserFooter

Data transfer between parent and child components

The most common way to transfer data between components in Vue is to use prop and EMIT to transfer values between parent and child components:

  • Pass data to child components through prop. Prop is a set of custom attributes registered on components. When a value is passed to a prop attribute, it becomes a property of that component instance.
  • Emit to pass data to the parent component. When a component needs to modify parent control data (which is passed in through props), based onOne way data flow principleChanges cannot be made within the component, but need to be made through$emit('eventName', value) Throw out custom eventsNotifies the parent layer of the data modification.

💡 For input components, such as BrowserFooter in a project that contains an element, you can allow the user to enter the name of the tag group. V-models can be used on components to achieve two-way binding of data at the same time that parent components pass values, that is, data can be passed from parent to child components (typically as the initial value of a variable), while receiving the value returned by the child component to modify the bound variable.

When components are used in the parent layer, two-way binding of data is implemented through the V-Model.

<! Using components, for simplicity of presentation, Omit bindings to show other attributes --> <BrowserFooter V-model :multi-on-group="multiOnGroup" V-model :new-group-name="newGroupName" />Copy the code

In the form element of the child component, bind the corresponding attribute and listen for the corresponding event:

  • for<input type="checkbox">Check box, the binding property ischecked, the event to listen for ischange
  • for<input type="text">Text input field, the binding property isvalue, the event to listen for isinput

When the property value changes, the event format needs to be prefixed with UPDATE:, and the property value is obtained by reading the corresponding property of the event object, $event.target

<template> <! -- omit other template content --> <! -- Single checkbox --> <! -- For the sake of simplicity, <input id="group" type="checkbox" :checked="multiOnGroup" @change="$emit('update:multiOnGroup', $event.target.checked)" > <label for="group" class="text-xs text-gray-500" > </label> <! -- omit other template content --> <! -- Text input box --> <! -- For the sake of simplicity, $emit('update:newGroupName'); $emit('update:newGroupName'); $emit('update:newGroupName'); $event.target.value)" > <! </template> <script> export default {props: {//... multiOnGroup: Boolean, newGroupName: { type: String, default: 'new', }, }, emits: [ // ... 'update:multiOnGroup', 'update:newGroupName', ], // ... } </script>Copy the code

Data transfer between grandparent and grandchild components

Provide and Inject values from an ancestor component to its descendants:

  • The parent component withprovideOptions or (in the case of composite apis) methods to provide data, you can provide reactive data, such as a REF object
  • Child components withinjectOption or (in the case of composite apis) method to receive this data

IndexedDB database is required for multiple components in the project. Create an instance of the database using the framework Dexie.js in the Vue entry file SRC /popup/main.js on the popup page, and provide it to other sub-components.

import Dexie from 'dexie';
import { createApp } from 'vue';
import App from './App.vue';
import '@/styles/tailwind.css';

const app = createApp(App);

const db = new Dexie('tagdown');
db.version(1).stores({
  bookmark: 'id, *tags, *groups'.share: 'id, share'.star: 'id, star'}); db.open().then((database) = > {
  app.provide('db', database);
  app.mount('#app');
}).catch((err) = > {
  // Error occurred
  console.log(err);
});
Copy the code

Then pass const db = inject(‘db’) in components such as BrowserPage.vue; To receive the database instance.

💡 For code maintenance purposes, when using reactive provide/Inject values, it is recommended that modifications to reactive property be limited to components that define provide as much as possible. If you really need to update inject data inside the data injection component, it is recommended to provide a method to change the value, so that the data value setting and data modification logic code are still centralized in the parent component for subsequent tracking and maintenance.

The parent component SRC /popup/ app. vue controls whether the pop-up Page is in the state of “browser” or “Edit” based on the reactive variable Page, while the button that controls this variable toggle is in the descendant component Browserfooter.vue.

The ancestor component therefore provides a way to modify this variable

<script>
import { ref, provide } from 'vue';
// ...
    
export default {
  // ...
  // provide page state and change the page state function
  provide('page', page);
  const changePage = (value) => {
    page.value = value;
  };
  provide('changePage', changePage);
  // ...
}
</script>
Copy the code

The method is received in the descendant component and bound to the button

<template> <! -- omit other template code --> <! Epage ('edit') > <button title="add bookmark" @click="changePage('edit')" > </template> <script> import {ref, watch, inject} from 'vue'; / /... export default { // ... const changePage = inject('changePage'); / /... return { // ... changePage } } </script>Copy the code

Information transfer between sibling components

For “parallel” relationships in the hierarchy, if data transfer is required, consider using Vue’s official recommended data state management plug-in Vuex.

One requirement in the project is to control the folder expansion/collapse of the sibling component BrowserGrid by clicking the component BrowserMenu button.

The parent of a sibling component is used as a “bridge” for information passing. When the parent listens for an event thrown by one of its children and then receives a custom event, the method of the other child component is called by a template reference to achieve sibling information passing.

Use the parent component BrowserPage as a “bridge” between two “parallel” child components.

<template> <! -- omit other template code --> <! -- menu --> <! -- For the sake of simplicity, <div> < Component :is="browserMenuComponent" @unfold-all="unfoldAllHandler" @fold-all="foldAllHandler" />  </div> <! -- omit other template code --> <! -- For the sake of simplicity, Omit bindings that show other attributes --> <BrowserGrid ref="grid" /> </template> <script> import {ref, computed, watch, inject,} from 'vue'; export default { // ... setup() { // unfold or fold all folder const grid = ref(null); Const unfoldAllHandler = () => {//... grid.value.unfoldAll(); // unfoldAll() //... }; const foldAllHandler = () => { // ... grid.value.foldAll(); // Call the component method foldAll() //... }; / /... return { // ... grid, unfoldAllHandler, foldAllHandler, } } } </script>Copy the code