Categories
JavaScript React

Add a PDF Reader to a React App with react-pdf-viewer

Adding React viewers is a common requirement for web apps. With React, there’s the react-pdf-viewer package that lets us add PDF viewers to React apps easily.

In this article, we’ll look at how to use it to add a PDF viewer to our React app.

Installation

We can install it by running:

npm install @phuocng/react-pdf-viewer

Basic Usage

After installing it, we can use it as follows:

import React from "react";
import Viewer, { Worker } from "@phuocng/react-pdf-viewer";

import "@phuocng/react-pdf-viewer/cjs/react-pdf-viewer.css";

export default function App() {
  return (
    <div className="App">
      <Worker workerUrl="https://unpkg.com/pdfjs-dist@2.2.228/build/pdf.worker.min.js">
        <div style={{ height: "750px" }}>
          <Viewer fileUrl="dummy.pdf" />
        </div>
      </Worker>
    </div>
  );
}

In the code above, we included the CSS file that comes with the package, and the Viewer for opening the PDF viewer and the Worker component for loading the PDF specified in the fileUrl prop as in the background.

We have to include the workerUrl prop with that URL so that the worker in that location is run.

Then we’ll get a PDF viewer that has zoom in and out controls, page navigation, document properties, and download options.

In a Create React App project, static files like PDFs should be in the public folder so that it can be loaded. Also, PDFs have to be in the same domain as the React app so that we won’t get CORS errors.

Options

There’re options for customization. We can change the layout of the sidebar, toolbar, and replace default controls with React components of our choice.

Layout options available include:

  • isSidebarOpened – boolean to indicate whether we want the sidebar to open or not
  • main – the main part of the viewer (a Slot component object)
  • toolbar – toolbar part (a RenderToolbar component object)
  • sidebarSlot object to define the sidebar of the viewer

For instance, we can write the following code to add a sidebar to display pages and a layout component do define a layout for our PDF viewer as follows:

import React from "react";
import Viewer, {
  Worker,
} from "@phuocng/react-pdf-viewer";

import "@phuocng/react-pdf-viewer/cjs/react-pdf-viewer.css";


export default function App() {
  const renderToolbar = (toolbarSlot) => {
    return (
      <div
        style={{
          alignItems: 'center',
          display: 'flex',
          width: '100%',
        }}
      >
        <div
          style={{
            alignItems: 'center',
            display: 'flex',
          }}
        >
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.searchPopover}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.previousPageButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.currentPageInput} / {toolbarSlot.numPages}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.nextPageButton}
          </div>
        </div>
        <div
          style={{
            alignItems: 'center',
            display: 'flex',
            flexGrow: 1,
            flexShrink: 1,
            justifyContent: 'center',
          }}
        >
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.zoomOutButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.zoomPopover}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.zoomInButton}
          </div>
        </div>
        <div
          style={{
            alignItems: 'center',
            display: 'flex',
            marginLeft: 'auto',
          }}
        >
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.fullScreenButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.openFileButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.downloadButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.moreActionsPopover}
          </div>
        </div>
      </div>
    );
  };

  const layout = (
    isSidebarOpened,
    main,
    toolbar,
    sidebar
  ) => {
    return (
      <div
        style={{
          border: '1px solid rgba(0, 0, 0, .3)',
          display: 'grid',
          gridTemplateAreas: "'toolbar toolbar' 'sidebar main'",
          gridTemplateColumns: '30% 1fr',
          gridTemplateRows: '40px calc(100% - 40px)',
          height: '100%',
          overflow: 'hidden',
          width: '100%',
        }}
      >
        <div
          style={{
            alignItems: 'center',
            backgroundColor: '#EEE',
            borderBottom: '1px solid rgba(0, 0, 0, .1)',
            display: 'flex',
            gridArea: 'toolbar',
            justifyContent: 'center',
            padding: '4px',
          }}
        >
          {toolbar(renderToolbar)}
        </div>
        <div
          style={{
            borderRight: '1px solid rgba(0, 0, 0, 0.2)',
            display: 'flex',
            gridArea: 'sidebar',
            justifyContent: 'center',
          }}
        >
          {sidebar.children}
        </div>
        <div
          {...main.attrs}
          style={Object.assign({}, {
            gridArea: 'main',
            overflow: 'scroll',
          }, main.attrs.style)}
        >
          {main.children}
        </div>
      </div>
    );
  };

  return (
    <Worker workerUrl="https://unpkg.com/pdfjs-dist@2.2.228/build/pdf.worker.min.js">
      <Viewer
        fileUrl='dummy.pdf'
        layout={layout}
      />
    </Worker>
  );
}

The default layout is the following:

┌───────────┬───────────┐
│ toolbar   │ toolbar   │
├───────────┼───────────┤
│ sidebar   │ main      │
└───────────┴───────────┘

In the code above, we reference those parts in the places we wish to place in the layout component. The children attribute has the parts of each component. attrs has the default props of each component, which we can change.

The renderToolbar function is a higher-order component that takes a toolbarSlot prop, which has the parts of the toolbar and we can place them accordingly according to our needs. In the example above, we put them in different divs and added our own styles to each div.

The grid above is a CSS grid, so we can modify the layout as we please and it’ll will in all modern browsers.

Those pats are passed in as props in the layout component. So we can reference them directly from the parameters of layout.

Conclusion

The react-pdf-viewer package is a very useful PDF viewer that’s designed with both performance and usability in mind. The default layout and controls are already very good. Performance comes from loading PDFs in the background with a web worker.

It’s also very customizable, we can define a layout component that has toolbar, sidebar and main as props and then we can customize them as we wish.

Categories
JavaScript Vue

Useful Vue Notification Components That’ll Save You Time

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at some Vue notification libraries that’ll save you time. They’re used for display popup notifications your way.

vue-notifications

vue-notifications is a library to let us display pop-up notifications without creating them ourselves.

To install it, we run:

npm i vue-notifications mini-toastr --save

Then we write the following code to import the library and use it:

main.js:

import Vue from "vue";
import App from "./App.vue";
import VueNotifications from "vue-notifications";
import miniToastr from "mini-toastr";
const toastTypes = {
  success: "success",
  error: "error",
  info: "info",
  warn: "warn"
};
miniToastr.init({ types: toastTypes });
const toast = ({ title, message, type, timeout, cb }) => {
  return miniToastr[type](message, title, timeout, cb);
};
const options = {
  success: toast,
  error: toast,
  info: toast,
  warn: toast
};
Vue.use(VueNotifications, options);
Vue.config.productionTip = false;
new Vue({
  render: h => h(App)
}).$mount("#app");

In the code above, we add the mini-toastr to configure and show toast popups. We added various types of toasts and then imported the VueNotifications dependency on the options as the type. The toast function calls miuniToastr to create the popup according to the types given.

The parameters in the toast callback has the following meanings:

  • title - string for the notification title
  • message - string for the message
  • timeout - number of milliseconds before the notification is gone
  • cb - callback function

App.vue :

<template>
  <div id="app">
    <button @click="show">Show Greeting</button>
  </div>
</template>
<script>
export default {
  name: "App",
  notifications: {
    showGreeting: {
      title: "Hello",
      message: "Hello world",
      type: "info"
    }
  },
  methods: {
    show() {
      this.showGreeting();
    }
  }
};
</script>

In the App component, we just add the notifications property with a property, which is the name of the function to display the message. Then inside, we set the title, message, andtype according to the ones listed in main.js .

Then when we click the button, we see a toast shown with Hello as the title and Hello world as the message in blue background.

vue-easy-toast

vue-easy-toast is very easy to use as its name suggests. To install it, we run:

npm install vue-easy-toast --save

Then to use it, we write the following code:

main.js :

import Vue from "vue";
import App from "./App.vue";
import Toast from "vue-easy-toast";
Vue.use(Toast);
Vue.config.productionTip = false;
new Vue({
  render: h => h(App)
}).$mount("#app");

App.vue :

<template>
  <div id="app">
    <button @click="showToast">Show Toast</button>
  </div>
</template>
<script>
export default {
  name: "App",
  methods: {
    showToast() {
      this.$toast("Toast.");
    }
  }
};
</script>

All we did was import the package with Vue.use and then call the $toast method added by vue-easy-toast to display a toast.

Then when we click the Show Toast button, we see the Toast. message displayed. We can also have HTML in our message string. Other options include:

  • id - string for a unique identifier
  • parent - string for the container to display the toast in
  • className - class name for the toast, can be a string or an array of strings
  • horizontalPosition - the horizontal position of the toast as a string
  • verticalPosition - vertical position of the toast as a string
  • duration - number of ms to display the toast
  • mode - a string that can take the value override or queue . If override , the last toast will forcibly flush previous.
  • closesable - boolean value for enabling closing toast manually
  • transition - string for built-in transformation name, which can be fade or slide-[up/down/left/right]

vue-notification

vue-notification is another easy to use notification library for showing notifications. To install it, we run:

npm install --save vue-notification

Then we can use it as follows:

main.js :

import Vue from "vue";
import App from "./App.vue";
import Notifications from "vue-notification";
Vue.use(Notifications);
Vue.config.productionTip = false;
new Vue({
  render: h => h(App)
}).$mount("#app");

App.vue :

<template>
  <div id="app">
    <notifications group="foo"/>
    <button @click="showToast">Show Toast</button>
  </div>
</template>
<script>
export default {
  name: "App",
  methods: {
    showToast() {
      this.$notify({
        group: "foo",
        title: "Important message",
        text: "Hello user!"
      });
    }
  }
};
</script>

We just have to import the library with Vue.use, and then we can call the $notify method that comes with vue-notification to display a message once we added the notification component to our template with the given group.

Then when we click the Show Toast button, we’ll see a blue popup with our message.

Conclusion

These libraries are all very useful for display notifications. Some libraries, like vue-easy-notification and vue-notification are easier than the other ones.

Categories
JavaScript Vue

Useful Vue Data Grid Components

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at some data grid components that we can use to build data grids.

vue-handsontable-official

This is a Vue data grid that looks and feels like a spreadsheet. It’s the official Vue wrapper for Handsontable.

We can run:

npm install handsontable @handsontable/vue

to install it. Then we can use it as follows:

<template>
  <div id="app">
    <hot-table
      licenseKey="non-commercial-and-evaluation"
      :data="data"
      :colHeaders="true"
      :rowHeaders="true"
      width="600"
      height="300"
    ></hot-table>
  </div>
</template>
<script>
import { HotTable } from "@handsontable/vue";
export default {
  data() {
    return {
      data: [
        ["", "Mazda", "Honda", "Toyota", "Volvo"],
        ["2019", 10, 11, 12, 13],
        ["2020", 20, 11, 14, 13],
        ["2021", 30, 15, 12, 13]
      ]
    };
  },
  components: {
    HotTable
  }
};
</script>
<style lang="scss">
@import "~handsontable/dist/handsontable.full.css";
</style>

In the code above, we registered the HotTable component in our component. Then we have the data model, which we pass int the data prop of the hot-table component.

Also, we set colHeaders and rowHeaders to true so that we see the column and row headings. This should result in a data grid that looks like a spreadsheet, with letters as column headers and numbers as row headers.

Like a spreadsheet, we can also add context menus for the cells by adding the contextMenu object as follows:

<template>
  <div id="app">
    <hot-table
      licenseKey="non-commercial-and-evaluation"
      :data="data"
      :colHeaders="true"
      :rowHeaders="true"
      width="600"
      height="300"
      :contextMenu="contextMenu"
    ></hot-table>
  </div>
</template>
<script>
import { HotTable } from "@handsontable/vue";
import Handsontable from "handsontable";
export default {
  data() {
    return {
      data: [
        ["", "Tesla", "Mercedes", "Toyota", "Volvo"],
        ["2019", 10, 11, 12, 13],
        ["2020", 20, 11, 14, 13],
        ["2021", 30, 15, 12, 13]
      ],
      contextMenu: {
        items: {
          row_above: {
            name: "Insert row above this one"
          },
          row_below: {},
          separator: Handsontable.plugins.ContextMenu.SEPARATOR,
          clear_custom: {
            name: "Clear all cells",
            callback() {
              this.clear();
            }
          }
        }
      }
    };
  },
  components: {
    HotTable
  }
};
</script>
<style lang="scss">
@import "~handsontable/dist/handsontable.full.css";
</style>

We added the contextMenu object, which has the row_above property for adding a context menu entry for inserting a new row above the current one. Then we added the row_below property to add an Insert row below menu item, but set the value to an empty object so that we keep the default settings.

Then we added a clear_custom property to add an entry to clear all cells.

There’re many more options, including customizing the editor, changing how cells are rendered, integration with Vuex and more.

The full list of options are at https://handsontable.com/docs/7.4.0/tutorial-introduction.html.

It’s free for non-commercial and evaluation purpose, but it’s a paid library for commercial purposes.

vue-sorted-table

vue-sorted-table is another table component that automatically generates a table from data. It makes adding the column sorting feature easy. To install it, we run:

npm install --save vue-sorted-table

Then to use it, we write the following code:

main.js :

import Vue from "vue";
import App from "./App.vue";
import SortedTablePlugin from "vue-sorted-table";
Vue.use(SortedTablePlugin);
Vue.config.productionTip = false;
new Vue({
  render: h => h(App)
}).$mount("#app");

App.vue :

<template>
  <div id="app">
    <sorted-table :values="values">
      <thead>
        <tr>
          <th scope="col" style="text-align: left; width: 10rem;">
            <sort-link name="id">ID</sort-link>
          </th>
          <th scope="col" style="text-align: left; width: 10rem;">
            <sort-link name="name">Name</sort-link>
          </th>
          <th scope="col" style="text-align: left; width: 10rem;">
            <sort-link name="hits">Age</sort-link>
          </th>
        </tr>
      </thead>
      <tbody slot="body" slot-scope="sort">
        <tr v-for="value in sort.values" :key="value.id">
          <td>{{ value.id }}</td>
          <td>{{ value.name }}</td>
          <td>{{ value.age }}</td>
        </tr>
      </tbody>
    </sorted-table>
  </div>
</template>
<script>
export default {
  name: "App",
  data: function() {
    return {
      values: [
        { name: "James", id: 2, age: 33 },
        { name: "Mary", id: 1, age: 42 },
        { name: "Alex", id: 3, age: 79 }
      ]
    };
  }
};
</script>

In the code above, we have the values array, which is used as the data for our sorted-table component. Then we add the table rows by using the regular HTML tr element.

In the table header, we add the sort-link component to each th element to let us sort each column individually.

We can also add icons for the up and down arrows used for the sort-link by passing in a second argument info Vue.use as follows:

Vue.use(SortedTablePlugin, {
  ascIcon: '<i class="material-icons">arrow_drop_up</i>',
  descIcon: '<i class="material-icons">arrow_drop_down</i>'
});

Also, the objects containing the values have to be a flat object. Other optional available include values , dir , sort , ascIcon , descIcon ,onSort . values is an array of objects containing the table values. dir is the sort direction, sort is the default sorting column, ascIcon and descIcon are the icons for the sort arrows, and onSort is a callback for custom sorting.

Conclusion

We can add tables easily with Vue Handsontable and vue-sorted-table . Vue Handsontable is more full-featured and works like a spreadsheet with its ability to edit cells and add/remove columns. vue-sorted-table is simpler. Its only purpose is to provide sorting on table columns.

Categories
JavaScript Vue

Add a Conditional Class to a Vue Component

We can conditionally add classes to a Vue component by using the v-bind:class directive. The shorthand for that is :class.

For instance, we can add it as follows:

<template>
  <div id="app">
    <button @click="isRed = !isRed">Toggle</button>
    <div :class="{red: isRed, green: !isRed}">foo</div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return { isRed: false };
  }
};
</script>

<style>
.red {
  color: red;
}

.green {
  color: green;
}
</style>

In the code above, we have:

:class="{red: isRed, green: !isRed}"

to set the class according to the value of isRed. Since it’s false initially, the green class will be applied. Then when we click the Toggle button for the first, isRed becomes true, so the red class will be applied.

In the style section, we specified that the red class has color red and green class has color green, so the div’s text will toggle between red and green as we click the button.

We can move the object to a computed property so that our template won’t have too much code. To do that we can write the following:

<template>
  <div id="app">
    <button @click="isRed = !isRed">Toggle</button>
    <div :class="classObj">foo</div>
  </div>
</template>

<script>
export default {
  name: "App",
  computed: {
    classObj() {
      return { red: this.isRed, green: !this.isRed };
    }
  },
  data() {
    return { isRed: false };
  }
};
</script>

<style>
.red {
  color: red;
}

.green {
  color: green;
}
</style>

We moved the object that passed into the :class directive to the function inside the computed property as follows:

classObj() {
  return { red: this.isRed, green: !this.isRed };
}

The value of classObj will update when this.isRed is updated, so we’ll get the same result as before.

In both examples, we have one conditional class applied to the div in our Vue component at one time.

Array Syntax

We can also define an array of classes, with the class names as the entries of the array. The condition for which the class is applied will be in the data object as the value of the properties with the given class names.

For instance, we can write the following code to do that:

<template>
  <div id="app">
    <button @click="isRed = !isRed">Toggle</button>
    <div :class="[isRed ? red: '', !isRed ? green: '']">foo</div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return { isRed: false, red: "red", green: "green" };
  }
};
</script>

<style>
.red {
  color: red;
}

.green {
  color: green;
}
</style>

In the code above, we have:

{ isRed: false, red: "red", green: "green" }

to place the class names as variables. Then we conditionally apply them by writing:

[isRed ? red: '', !isRed ? green: '']

in the template. Therefore, we’ll see the same result as before since the conditions and classes are the same as before. It’s just that we used the array syntax instead of the object syntax.

Conditionally Applying Classes With Vue Components

We can apply the :class directive directly on our own components or 3rd party ones. To do that, we use the same syntax. For instance, we can write the following code to do that:

components/Foo.vue:

<template>
  <p>foo</p>
</template>

App.vue:

<template>
  <div id="app">
    <button @click="isRed = !isRed">Toggle</button>
    <Foo :class="[isRed ? red: '', !isRed ? green: '']"/>
  </div>
</template>

<script>
import Foo from "./components/Foo.vue";

export default {
  name: "App",
  components: {
    Foo
  },
  data() {
    return { isRed: false, red: "red", green: "green" };
  }
};
</script>

<style>
.red {
  color: red;
}

.green {
  color: green;
}
</style>

The classes we applied conitionally will still be applied to the roo element of Foo automatically.

We can also do the same with the object syntax as follows:

App.vue:

<template>
  <div id="app">
    <button @click="isRed = !isRed">Toggle</button>
    <Foo :class="classObj"/>
  </div>
</template>

<script>
import Foo from "./components/Foo.vue";

export default {
  name: "App",
  components: {
    Foo
  },
  computed: {
    classObj() {
      return { red: this.isRed, green: !this.isRed };
    }
  },
  data() {
    return { isRed: false, red: "red", green: "green" };
  }
};
</script>

<style>
.red {
  color: red;
}

.green {
  color: green;
}
</style>

They both get the same results.

Conclusion

We can apply a class conditionally with the :class or v-bind:class directive. We can set the value of it as an object or an array.

To make our template cleaner, we can put our class code in a computed property so that it’ll be updated when any piece of component data updates.

A conditional class can be applied to elements and components alike.

Categories
JavaScript

The Ultimate Guide for Manipulating Numbers in JavaScript

Rounding Numbers

JavaScript has multiple ways to round a number. Some choices are Math.round, number.toFixed, and number.toPrecision. You can also write your own function to round a number up or down to the nearest increment.


Math.round

Math.round rounds a number to the nearest integer. If the decimal part of the number is less than 0.5, it is rounded down. Otherwise, if the decimal part of the number is 0.5 or higher it will be rounded up.

The function returns the rounded number as the value.

For example:

Math.round(12.5); // 13  
Math.round(12.49); // 12

Number.toFixed

You can set the number of digits that appears after the decimal place with the toFixed function. The function returns the string representation of the number as the value.

It can be used like this:

const a = 12.8888888888;  
const b = a.toFixed(2); // 12.88

Number.toPrecision

Number.toPrecision is similar to toFixed.

It returns the string representation of a number but you can round it to the specified number of significant digits, which you can specify or let it automatically round to the correct number of significant digits.

const a = 12.8888888888;  
const b = a.toPrecision(2); // 13, since 2 significant digits is specified  
const c = a.toPrecision(); // 12.8888888888, since all digits are significant in the original number

Round to the Nearest Increment

You can round to the nearest increment, up or down, as you specify:

const roundNumberUp = (num, increment) => {   
  return Math.ceil(num / increment) * increment;  
}  
console.log(roundNumberUp(12.2, 0.5)) // 12.5

What this does is it takes the original number, divides it by the increment you want to round up to, then takes the ceiling of that, then multiplies by the increment. This means that the number should always round up.

Similarly, you can round down to the nearest increment with floor.

const roundNumberUp = (num, increment) => {   
  return Math.floor(num / increment) * increment;  
}  
console.log(roundNumberUp(12.2, 0.5)) // 12.5

What this does is it takes the original number, divides it by the increment you want to round up to, then takes the floor of that, then multiplies by the increment. This means the number should always round down.


Exponentiation

There are multiple ways to compute exponents with JavaScript.

The newest way is the exponentiation operator **, available with ES2016 or higher.

For example, we can do this:

const a = 2 ** 3; // 8

It is right associative, so a ** b ** c is equal to a ** (b ** c). This works with all exponents.

For example:

const a = 2 ** (3 ** 4);  
const b = 2 ** 3 ** 4;  
a == b // true, both are 2.4178516392292583e+24

Detailed browser compatibility is available on the Mozilla website.

We can also use the Math.pow function, like this:

const a = Math.pow(2,3) // 8

It takes two arguments, the first is the base and the second is the exponent. Math.pow works with all exponents.

Math.pow is compatible with all recent browsers.


Rounding Numbers

JavaScript has multiple ways to round a number. Some choices are: Math.round, number.toFixed, and number.toPrecision. You can also write your own function to round a number up or down to the nearest increment.


Math.round

Math.round rounds a number to the nearest integer.

If the decimal part of the number is less than 0.5, it is rounded down. Otherwise, if the decimal part of the number is 0.5 or higher, it will be rounded up. The function returns the rounded number as the value.

For example:

Math.round(12.5); // 13  
Math.round(12.49); // 12

Number.toFixed

You can set the number of digits that appear after the decimal place withthe toFixed function. The function returns the string representation of the number as the value. It can be used like this:

const a = 12.8888888888;  
const b = a.toFixed(2); // 12.88

Number.toPrecision

Number.toPrecision is similar to toFixed.

It returns the string representation of a number but you can round it to the specified number of significant digits, which you can specify or let it automatically round to the correct number of significant digits.

const a = 12.8888888888;  
const b = a.toPrecision(2); // 13, since 2 significant digits is specified  
const c = a.toPrecision(); // 12.8888888888, since all digits are significant in the original number

Round to the Nearest Increment

You can round to the nearest increment up or down you specify:

const roundNumberUp = (num, increment) => {   
  return Math.ceil(num / increment) * increment;  
}  
console.log(roundNumberUp(12.2, 0.5)) // 12.5

What this does is take the original number, divide by the increment you want to round up to, then take the ceiling of that, then multiply by the increment. This means the number should always round up.

Similarly, you can round down to the nearest increment with floor.

const roundNumberUp = (num, increment) => {   
  return Math.floor(num / increment) * increment;  
}  
console.log(roundNumberUp(12.2, 0.5)) // 12.5

What this does is take the original number, divide by the increment you want to round up to, then take the floor of that, then multiply by the increment. This means the number should always round down.