Categories
JavaScript Answers

How to validate currency with JavaScript regex?

Sometimes, we want to validate currency with JavaScript regex.

In this article, we’ll look at how to validate currency with JavaScript regex.

How to validate currency with JavaScript regex?

To validate currency with JavaScript regex, we use the following regex:

/(?=.*?\d)^\$?(([1-9]\d{0,2}(,\d{3})*)|\d+)?(\.\d{1,2})?$/

The (?=.*?\d) part makes sure that the string starts with a number.

^\$?(([1-9]\d{0,2} makes sure the number starts with 1 to 9.

(,\d{3})*) checks that the number has 0 or more groups of 3 digits after the start of the number separated by commas.

We also have \d+ to make the 3 digit groups optional.

Finally, we have ?(\.\d{1,2})?$ to make sure that the number optionally ends with a decimal point and 0 to 2 digits.

Conclusion

To validate currency with JavaScript regex, we use the following regex:

/(?=.*?\d)^\$?(([1-9]\d{0,2}(,\d{3})*)|\d+)?(\.\d{1,2})?$/
Categories
JavaScript Answers

How to convert \n to HTML line breaks?

Sometimes, we want to convert \n to HTML line breaks.

In this article, we’ll look at how to convert \n to HTML line breaks.

How to convert \n to HTML line breaks?

To convert \n to HTML line breaks, we can set the white-space CSS property to pre-line.

For instance, we write:

<span style="white-space: pre-line"></span>

to add a span with the white-space CSS property set to pre-line.

Then we write:

const span = document.querySelector('span')
span.textContent = 'foo \n bar'

We select the span with document.querySelector.

And then we set the textContent of the span to 'foo \n bar' to set that as the content of the span.

Now we should see:

foo
bar

displayed on the screen.

Conclusion

To convert \n to HTML line breaks, we can set the white-space CSS property to pre-line.

Categories
Chart.js JavaScript Answers

How to remove the vertical line in the Chart.js line chart?

Sometimes, we want to remove the vertical line in the Chart.js line chart.

In this article, we’ll look at how to remove the vertical line in the Chart.js line chart.

How to remove the vertical line in the Chart.js line chart?

To remove the vertical line in the Chart.js line chart, we can set the options.scales.x.grid.display property to false.

For instance, we write:

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>

<canvas id='myChart' style='width: 300px; height: 300px'></canvas>

to add the Chart.js script and canvas for the chart.

Then we write:

const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
  type: 'line',
  data: {
    labels: ['Monday', 'Tuesday', 'Wednesday'],
    datasets: [{
      label: '# of Votes',
      data: [12, 19, 3],
      borderWidth: 1,
      borderColor: 'green'
    }]
  },
  options: {
    scales: {
      x: {
        grid: {
          display: false
        }
      },
    }
  }
});

to set options.scale.x.grid.display to false to hide the vertical lines in the background.

Now we should only see the horizontal lines displayed in the background.

Conclusion

To remove the vertical line in the Chart.js line chart, we can set the options.scales.x.grid.display property to false.

Categories
Vue Answers

How to add web workers to a Vue app created with Vue-CLI?

Sometimes, we want to add web workers to a Vue app created with Vue-CLI.

In this article, we’ll look at how to add web workers to a Vue app created with Vue-CLI.

How to add web workers to a Vue app created with Vue-CLI?

To add web workers to a Vue app created with Vue-CLI, we can use the worker-plugin package.

To install it, we run:

npm i worker-plugin

Next, ion vue.config.js in the project root, we add:

const WorkerPlugin = require("worker-plugin");

module.exports = {
  configureWebpack: {
    output: {
      globalObject: "this"
    },
    plugins: [new WorkerPlugin()]
  }
};

to load the worker-plugin.

Then we create the workers folder in the src folder with the following 2 files:

worker.js

onmessage = (e) => {
  const {data} = e
  console.log(data);
  postMessage({ foo: "bar" });
};

We call postMessage to send a message to the component that invoked the worker.

index.js

const worker = new Worker("./worker.js", { type: "module" });

const send = (message) =>
  worker.postMessage({
    message
  });

export default {
  worker,
  send
};

Next, we use the worker in a component by writing the following in src/App.vue:

<template>
  <div id="app"></div>
</template>

<script>
import w from "@/workers";

export default {
  name: "App",
  beforeMount() {
    const { worker, send } = w;
    worker.onmessage = (e) => {
      const { data } = e;
      console.log(data);
    };
    send({ foo: "baz" });
  },
};
</script>

We import the workers/index.js with:

import w from "@/workers";

Then we destructure the worker from the imported module with:

const { worker, send } = w;

Then we add a message handler to it with:

worker.onmessage = (e) => {
  const { data } = e;
  console.log(data);
};

And then we send a message to the worker with:

send({ foo: "baz" });

Now we get {foo: 'bar'} from the worker in the worker.onmessage in App.

And in worker.js‘s onmessage function, we get:

{
  message: {
    foo: 'baz'
  }
}

Conclusion

To add web workers to a Vue app created with Vue-CLI, we can use the worker-plugin package.

Categories
JavaScript Nuxt.js Vue

How to run background tasks in a Nuxt app with web workers?

(The source code is at https://github.com/jauyeunggithub/bravado-quest)

Sometimes, we want to run background tasks in a Nuxt app with web workers.

In this article, we’ll look at how to run background tasks in a Nuxt app with web workers.

How to run background tasks in a Nuxt app with web workers?

To run background tasks in a Nuxt app with web workers, we can add the Webpack’s worker-loader package into our Nuxt project.

Also, we’ve to make sure that web workers are only loaded on client side.

We’ll make a project that loads a large JSON file and searches it.

To start, we create the Nuxt project with:

npx create-nuxt-app quest

Then we run:

cd quest
npm run dev

to change to the project folder and run the project.

Next, we add Vuetify into the project with:

npm install @nuxtjs/vuetify -D

And we add the worker-loader package with:

npm i worker-loader@^1.1.1

To store data with IndexedDB, we install the Dexie package.

To install it, we run:

npm i dexie

Next, in nuxt.config.js, we change it to:

export default {
  // Global page headers: https://go.nuxtjs.dev/config-head
  head: {
    title: 'quest',
    htmlAttrs: {
      lang: 'en',
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' },
      { name: 'format-detection', content: 'telephone=no' },
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
  },

  // Global CSS: https://go.nuxtjs.dev/config-css
  css: [],

  // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
  plugins: [
    { src: '~/plugins/inject-ww', ssr: false }
  ],

  // Auto import components: https://go.nuxtjs.dev/config-components
  components: true,

  // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
  buildModules: ['@nuxtjs/vuetify'],

  // Modules: https://go.nuxtjs.dev/config-modules
  modules: [],

  // Build Configuration: https://go.nuxtjs.dev/config-build
  mode: 'spa',
  build: {
    extend(config, { isClient }) {
      config.output.globalObject = 'this'

      if (isClient) {
        config.module.rules.push({
          test: /\.worker\.js$/,
          loader: 'worker-loader',
          exclude: /(node_modules)/,
        })
      }
    },
  },
  ssr: false,
}

We added the build.extend method to invoke the worker-loader when any file that ends with .worker.js is found.

isClient makes sure workers only load on client side apps.

config.output.globalObject = 'this' is needed so the hot reloading will work with the web workers loaded.

We also set ssr to false to disable server side rendering.

Also, we add { src: '~/plugins/inject-ww', ssr: false } to add the /plugins/inject-ww to load the worker loading plugin.

We add '@nuxtjs/vuetify' to load Vuetify in Nuxt.

In the plugins folder, we add:

import Worker from '~/assets/js/data.worker.js'

export default (_, inject) => {
  inject('worker', {
    createWorker () {
      return new Worker()
    }
  })
}

to load the worker from the /assets/js/data.worker.js.

To create worker, we create the data.worker.js file in the /assets/js/ and add:

import db from '@/db'

onmessage = async () => {
  if ((await db.avatars.toCollection().toArray()).length > 0) {
    postMessage({
      loaded: true,
    })
    return
  }
  const usersObj = await import(`@/data/users.json`)
  const users = Object.values(usersObj)
  const usersWithId = users.map((u, id) => ({ ...u, id }))
  await db.avatars.bulkPut(usersWithId)
  postMessage({
    loaded: true,
  })
}

@/data/users.json is a big JSON file saved in the project.

We import the users.json file from the data folder and store it in Indexed DB with Dexie.

db comes from db.js, which has:

import Dexie from 'dexie'

const db = new Dexie('db')
db.version(1).stores({
  avatars: '++id, address, avatar, city, email, name, title',
})

export default db

We create the db database with the avatars collection with the given keys.

We call bulkPut to write the retrieved data to the collection.

And we call postMessage to communicate that loading is done.

The message will be picked by by the message event handler when we invoke this worker.

In the pages, folder, we add _keyword.vue, to add an input and the search results component:

<template>
  <v-app>
    <v-card width="600px" elevation="0" class="mx-auto">
      <v-card-text :elevation="0">
        <v-text-field
          v-model="keyword"
          hide-details
          :prepend-inner-icon="mdiMagnify"
          full-width
          solo
          dense
          background-color="#FAFAFA"
        />

        <SearchResults :keyword="keyword" />
      </v-card-text>
    </v-card>
  </v-app>
</template>

<script >
import { mdiMagnify } from '@mdi/js'
import SearchResults from '@/components/SearchResults'

export default {
  components: {
    SearchResults,
  },
  data() {4
    return {
      keyword: '',
      mdiMagnify,
    }
  },
  beforeMount() {
    this.keyword = this.$route.params.keyword
  },
}
</script>

<style>
html {
  overflow: hidden;
}
</style>

Then in components/SearchResults.vue that we created, we add:

<template>
  <div>
    <div v-if="loading" class="my-3">
      <v-card>
        <v-card-text> Loading... </v-card-text>
      </v-card>
    </div>
    <div v-else id="scrollable" class="my-3">
      <v-card
        v-for="r of filteredSearchResultsWithHighlight"
        :key="r.id"
        class="my-3"
        :style="{
          border: highlightStatus[r.id] ? '2px solid lightblue' : undefined,
        }"
        @click="
          highlightStatus = {}
          $set(highlightStatus, r.id, !highlightStatus[r.id])
        "
      >
        <v-card-text class="d-flex pa-0 ma-0">
          <img :src="r.avatar" class="avatar" />
          <div
            class="flex-grow-1 pa-3 d-flex flex-column justify-space-between right-pane"
          >
            <div class="d-flex justify-space-between">
              <div>
                <h2 v-html="r.name"></h2>
                <p class="py-0 my-0">
                  <b v-html="r.title"></b>
                </p>
                <p class="py-0 my-0">
                  <span v-html="r.address"></span>,
                  <span v-html="r.city"></span>
                </p>
              </div>
              <div v-html="r.email"></div>
            </div>

            <div>
              <v-btn text color="#00897B">Mark as Suitable</v-btn>
            </div>
          </div>
        </v-card-text>
      </v-card>
    </div>
  </div>
</template>

<script>
import db from '@/db'

export default {
  props: {
    keyword: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      highlightStatus: {},
      filteredSearchResults: [],
      loading: false,
    }
  },
  computed: {
    filteredSearchResultsWithHighlight() {
      const { keyword } = this
      if (!Array.isArray(this.filteredSearchResults)) {
        return []
      }
      const highlighted = this.filteredSearchResults?.map((u) => {
        const highlightedEntries = Object.entries(u)?.map(([key, val]) => {
          if (key === 'avatar' || key === 'id') {
            return [key, val]
          }
          const highlightedVal = val?.replace(
            new RegExp(keyword, 'gi'),
            (match) => `<mark>${match}</mark>`
          )
          return [key, highlightedVal]
        })
        return Object.fromEntries(highlightedEntries)
      })
      return highlighted
    },
  },
  watch: {
    keyword: {
      immediate: true,
      handler() {
        this.search()
      },
    },
  },
  beforeMount() {
    this.loadData()
  },
  methods: {
    async search() {
      await this.$nextTick()
      const { keyword } = this
      if (keyword) {
        const filteredSearchResults = await db.avatars
          .filter((u) => {
            const { address, city, email, name, title } = u
            return (
              address?.toLowerCase()?.includes(keyword?.toLowerCase()) ||
              city?.toLowerCase()?.includes(keyword?.toLowerCase()) ||
              email?.toLowerCase()?.includes(keyword?.toLowerCase()) ||
              name?.toLowerCase()?.includes(keyword?.toLowerCase()) ||
              title?.toLowerCase()?.includes(keyword?.toLowerCase())
            )
          })
          .limit(10)
          .toArray()
        this.filteredSearchResults = filteredSearchResults
      } else {
        this.filteredSearchResults = await db.avatars
          ?.toCollection()
          ?.limit(10)
          .toArray()
      }
    },
    loadData() {
      this.loading = true
      const worker = this.$worker.createWorker()
      worker.onmessage = () => {
        this.search()
        this.loading = false
      }
      worker.postMessage('load')
    },
  },
}
</script>

<style>
.avatar {
  background: #bdbdbd;
  width: 150px;
}

.right-pane {
  background: #fafafa;
}

mark {
  background: yellow;
}

#scrollable {
  height: calc(100vh - 100px);
  overflow-y: auto;
}
</style>

In the loadData method. we call this.$worker.createWorker to create the worker that we injected.

this.$worker is available since the inject-ww.js script is run when the app is loaded.

We set worker.onmessage to a function so that we recent messages from the worker when postMessage in the onmessage function of the worker is called.

Once we received a message from the web workwer, we call the this.search method to do the filtering according to the keyword value.

And we call worker.postMessage with anything so that the worker’s onmessage function in the worker will start running.

Now when we type in something into the text box, we see results displayed in the cards.

We should be able to load large amounts of data in the web worker without hanging the browser since it’s run in the background.

Conclusion

To run background tasks in a Nuxt app with web workers, we can add the Webpack’s worker-loader package into our Nuxt project.

Also, we’ve to make sure that web workers are only loaded on client side.

We’ll make a project that loads a large JSON file and searches it.