Categories
Vuetify

Vuetify — Bottom Nav Bar

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Horizontal Nav Bar

We can add the horizontal prop to make the bottom nav text show beside the icon instead of below it:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-bottom-navigation :value="activeBtn" color="purple lighten-1" horizontal>
          <v-btn>
            <span>History</span>
            <v-icon>mdi-history</v-icon>
          </v-btn>

          <v-btn>
            <span>Favorites</span>
            <v-icon>mdi-heart</v-icon>
          </v-btn>

          <v-btn>
            <span>Map</span>
            <v-icon>mdi-map-marker</v-icon>
          </v-btn>
        </v-bottom-navigation>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    activeBtn: undefined,
  }),
};
</script>

Shift

The shift prop lets us hide the button text until it’s active.

The v-btn text in the bar should be wrapped with a span tag.

For instance, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-bottom-navigation :value="activeBtn" color="purple lighten-1" shift>
          <v-btn>
            <span>History</span>
            <v-icon>mdi-history</v-icon>
          </v-btn>

          <v-btn>
            <span>Favorites</span>
            <v-icon>mdi-heart</v-icon>
          </v-btn>

          <v-btn>
            <span>Map</span>
            <v-icon>mdi-map-marker</v-icon>
          </v-btn>
        </v-bottom-navigation>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    activeBtn: undefined,
  }),
};
</script>

We just add a shift prop to make the text show only when an icon is clicked.

Toggle

The display state of the v-bottom-navigation can be toggled with the input-value prop.

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <div class="overflow-hidden">
          <div class="text-center mb-2">
            <v-btn text color="deep-purple" @click="showNav = !showNav">Toggle Nav</v-btn>
          </div>

<v-bottom-navigation :value="activeBtn" color="purple lighten-1" :input-value="showNav">
            <v-btn>
              <span>History</span>
              <v-icon>mdi-history</v-icon>
            </v-btn>

            <v-btn>
              <span>Favorites</span>
              <v-icon>mdi-heart</v-icon>
            </v-btn>

            <v-btn>
              <span>Map</span>
              <v-icon>mdi-map-marker</v-icon>
            </v-btn>
          </v-bottom-navigation>
        </div>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    activeBtn: undefined,
    showNav: false,
  }),
};
</script>

We added a v-btn to toggle the nav.

When showNav is true then it’s shown.

Otherwise, it’s hidden.

We set the input-value to show the nav.

Hide on Scroll

We can show the v-bottom-navigation only when the target element is scrolled.

To do that, we add the hide-on-scroll prop with the scroll-target prop set to the selector of them item we’re scrolling.

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-card class="overflow-hidden mx-auto" height="200" max-width="500">
          <v-bottom-navigation scroll-target="#scroll-area" hide-on-scroll absolute horizontal>
            <v-btn text color="deep-purple accent-4">
              <span>History</span>
              <v-icon>mdi-history</v-icon>
            </v-btn>

            <v-btn text color="deep-purple accent-4">
              <span>Favorites</span>
              <v-icon>mdi-heart</v-icon>
            </v-btn>

            <v-btn text color="deep-purple accent-4">
              <span>Map</span>
              <v-icon>mdi-map-marker</v-icon>
            </v-btn>
          </v-bottom-navigation>

          <v-sheet id="scroll-area" class="overflow-y-auto" max-height="600">
            <v-container style="height: 1500px;"></v-container>
          </v-sheet>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    activeBtn: undefined,
    showNav: false,
  }),
};
</script>

The scroll-target is added to the v-bottom-navigation component.

hide-on-scroll makes it hide on scroll.

Conclusion

We can make the bottom nav bar behave the way we want with Vuetify.

Categories
Vuetify

Vuetify — Transition, Alert, and Scrolling

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Custom Transition

We can create our own transition by using the createSimpleTransition function to create our transition.

First, we define the component in vuetify.js

import Vue from 'vue';
import Vuetify from 'vuetify/lib';
import { createSimpleTransition } from 'vuetify/lib/components/transitions/createTransition'

const fadeTransition = createSimpleTransition('v-fade-transition')
Vue.component('v-fade-transition', fadeTransition)
Vue.use(Vuetify);

export default new Vuetify({
});

Then we write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-btn class="ma-2" color="primary" @click="expand = !expand">Expand Transition</v-btn>

        <v-fade-transition>
          <v-card v-show="expand" height="100" width="100" class="mx-auto"></v-card>
        </v-fade-transition>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    expand: false,
  }),
};
</script>

<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
</style>

We defined the v-fade-transition component with:

const fadeTransition = createSimpleTransition('v-fade-transition')
Vue.component('v-fade-transition', fadeTransition)

Then we defined the classes for it with:

<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
</style>

The prefix fade should be the same one as the word between v- and -transition so that the transition styles will be applied.

Programmatic Scrolling

We can scroll our page programmatically with Vuetify.

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-btn ref="button" block color="primary" @click="$vuetify.goTo('#num-100')">scroll</v-btn>

        <p v-for='n in 100' :key='n' :id="`num-${n}`">{{n}}</p>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({}),
};
</script>

We have the p elements with some IDs.

And we call $vuetify.goTo to scroll to the element with the given selector.

goTo also takes a second argument with some options.

The option object can have the duration , offset , and easing properties.

We can set the options by writing:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-btn ref="button" block color="primary" @click="$vuetify.goTo('#num-100', options)">scroll</v-btn>

        <p v-for="n in 100" :key="n" :id="`num-${n}`">{{n}}</p>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    options: {
      duration: 1300,
      offset: 0,
      easing: "easeInOutCubic",
    },
  }),
};
</script>

And we set the option for scrolling.

Alerts

We can add an alert with the v-alert component.

It comes with 4 default styles, which are success , info , warning , and error .

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-alert type="success">success alert.</v-alert>

        <v-alert type="info">info alert.</v-alert>

        <v-alert type="warning">warning alert.</v-alert>

        <v-alert type="error">error alert.</v-alert>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({}),
};
</script>

We have the v-alert component with the alerts.

Conclusion

We can scroll programmatically and add alerts with Vuetify.

Also, we can create our own transition components with one function.

Categories
TypeScript Best Practices

TypeScript Best Practices — Interfaces vs Type Aliases and Unnecessary Syntax

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Class Name Casing

TypeScript class names should be PascalCase.

This also applies to interfaces.

So we write:

class Foobar {}

and:

interface FooBar {}

Use UTF-8 Encoding for Files

We should use UTF-8 encoding for files.

This way, there won’t be any issues using the file anywhere.

File Name Casing

We should name our files with a consistent case.

We can stick with camel case, Pascal case, kebab case, or snake case.

Camel case is fileName.ts .

Pascal case is FileName.ts ,

Kebab case is file-name.ts .

Snake case is file_name.ts .

We just stick with one for all files.

Use Explicit Increment or Decrement Operators

We should use explicit increment or decrement operators to make sure that we just assign the new value and don’t use the return value.

For instance, instead of writing:

++i;
i++;
--j;
j--;

which return the value and do the increment or decrement operation, we write:

i += 1;
i -= 1;
j += 1;
j -= 1;

Interface

We may want to have interfaces that start with I to distinguish them from other entities.

For instance, we may want to write:

interface IFoo {
  bar: string;
}

Use Interface Over Type Literal

Interfaces can be implemented, extended, and merged, so they’re preferred to type literals.

For instance, instead of writing:

type Alias = {
  num: number
}

We write:

interface Foo {
  num: number;
}

Match Default Export Name

If we have a default export, then our default import should match the name of the export.

This reduces confusion.

So if we have:

bar.ts

export default foo;

Then we write:

import foo from bar;

Newline Per Chained Call

If we have a chain of method calls, then we may want to put each of them in a new line so that it won’t overflow the page.

For instance, instead of writing:

foo.bar().baz();

We write:

foo
  .bar()
  .baz();

No Angle Bracket Type Assertion

We should use as for type assertions since we can use it for type assertions in .tsx files in addition to .ts files.

So instead of writing:

const foo = <Bar>bar;

We write:

const foo = bar as Bar;

No Boolean Literal Comparison

We shouldn’t compare with boolean literals since they’re redundant.

For instance, we can shorten:

if (x === true)

to:

if (x)

Use of Parameter Properties

We can have parameter properties in the TypeScript code.

They let us pass in constructor parameter and assign it to a value at the same time.

Instead of writing:

class Animal {
  constructor(private numLegs: number) {}
}

We can write:

class Animal {
  private numLegs: number = 2;
  constructor(numLegs: number) {
    this.numLegs = numLegs;
  }
}

No Reference Import

We shouldn’t use reference if we use import .

For instance, if we have:

<reference path="foo.bar" />

We write:

import { bar } from 'foo';

It’s more standard and we don’t need reference to pull type definitions out of type definition files anymore.

No Useless Callback Wrappers

We shouldn’t have useless callback wrappers in our code.

For instance, instead of writing:

const handleContent = (content) => console.log('do something with', content);

promise.then((content) => handleContent(content))

We write:

const handleContent = (content) => console.log('do something with', content);

promise.then(handleContent)

It’s a lot cleaner.

No Unnecessary Initializers

We shouldn’t have to set variables to undefined .

For instance, instead of writing:

let x =  undefined;

We write:

let x;

No Object Literal Key Quotes

If an object literal has valid identifiers as property names, then we don’t need quotes around the names.

For instance, instead of writing:

const obj = { 'foo': 1 };

We write:

const obj = { foo: 1 };

Conclusion

We should have a consistent file name casing.

Also, we shouldn’t have unnecessary syntax in our code.

Imports should be used to pull data type definitions instead from external sources.

Useless callback wrappers should also be removed.

Interfaces should be used instead of type alias since they’re more versatile.

Categories
TypeScript Best Practices

TypeScript Best Practices — Namespace, any, for-in and More

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Group Function Overloads Together

We can overload functions.

This means we can have multiple function signatures for a function, which TypeScript can check for.

To make our lives easier, we should group them together so that it’s easier to read:

function foo(a: string, b: string): string;

function foo(a: number, b: number): number;

function foo(a: any, b: any): any {
  console.log(a, b);
}

Visibility Declarations for Class Members

To take advantage of the access control capabilities of TypeScript, we can add the visibility declarations or class members.

For instance, we write:

class Employee {
  private getSalary(): number {
    return 90000;
  }
}

We added the private access modifier so that getSalary can only be called by other methods in the class.

There’s also the public modifier to make the member available to outside code.

protected makes the member available to subclasses and the current class.

public is the default.

We can also do the same for instance variables:

class Employee {
  private empCode: number;
}

Ordering Class Members

We may consider ordering class members to make the members easier to read.

We can order them by access modifiers, alphabetical order, etc.

It’s good to stick with one.

For instance, instead of writing:

class Employee {
  private id: string;
  private empCode: number;
  private empName: string;
}

We write:

class Employee {
  private empCode: number;
  private empName: string;
  private id: string;
}

to sort them by alphabetical order.

Eliminate the Use of any Types

We can eliminate the use of any types in our code to take advantage of TypeScript’s type-checking capabilities.

If we need something more flexible than static types, there are many ways to define them.

We can use literal types to restrict values to only some literals.

There’re union types to let us check for members for multiple types.

The intersection type makes sure that the variable has members that are in both types.

Index signatures let us check for dynamic properties.

There’re many ways to avoid any .

For instance, instead of writing:

let bar: any;

We write:

let foo: string;

No Empty Interface

We shouldn’t have empty interfaces in our code since they’re useless.

So instead of writing:

interface I {}

We write:

interface I {
  bar: number;
}

No for-in Loops

for-in loops are legacy JavaScript syntax which has better modern alternatives.

It’s bad since we need to use the hasOwnProperty to check for non-inherited properties with it.

Better alternatives include Object.keys to get the non-inherited keys of an object.

Object.values to get the values and Object.entries to get all entries.

So instead of writing:

for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(obj[key]);
  }
}

We write:

for (const key in Oject.keys(obj)) {
  console.log(obj[key]);
}

No Import Side Effects

import should be used for import module members and using them.

If they perform side effects, then it’s not good because it’s hard to statically analyze the code.

So instead of writing:

import 'foo';

We write:

import { bar } from 'foo';

Some exceptions may be importing CSS with Webpack as modules.

So we may still write:

import 'styles.css';

No Explicit Type Declarations for Variable or Parameters with Literal Values

It’s redundant to have type declarations for variables or parameters that are assigned with numbers strings or boolean.

TypeScript can check these without an explicit type.

Therefore, instead of writing:

const foo: number = 10;

We write:

const foo = 10;

Don’t Use module Keyword for Namespaces

If we declare namespaces, then we should use the namespace keyword.

Instead of writing:

module Math {
  function add(a: number, b: number): number {
    return a + b;
  }
}

We write:

namespace Math {
  function add(a: number, b: number): number {
    return a + b;
  }
}

This way, we won’t confuse what we have with ES modules.

Conclusion

Grouping things together make them easier to read.

Visibility modifiers are a useful TypeScript feature.

any types can be replaced with many things.

Use namespace to declare namespaced code.

Categories
TypeScript Best Practices

TypeScript Best Practices — Function and Array Types, Reducing Complexity

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Use isNaN to Check for NaN

NaN doesn’t equal itself, so we can’t use === or !== to check for NaN .

Instead, we use the isNaN function to check for it.

So instead of writing:

if (foo === NaN) {}

We write:

if (isNaN(foo)) {}

Invalid Use of void

We should watch for invalid uses of void .

We shouldn’t mix void with other types since void means nothing rather than an explicit type of value.

If we need undefined , then we should use the undefined type.

So types expressions like:

Foo | void

should be replaced with:

Foo | undefined

Max Number of Classes Per File

We shouldn’t have too many classes per file.

It’s probably too complex if we do.

We can set the max number of classes per file with a linter.

Max File Line Count

If a file has too many lines, then it’s probably too complex and hard to read.

We can set the max file line count in a file with a linter.

Default Exports

Default exports aren’t as clear, so we should avoid using them.

Named exports are clearer.

So we may want to avoid them.

Default Import

Like default exports, default imports aren’t as clear as named imports.

No Duplicate Imports

We can combine multiple import statements from the same module into one import statement, so we should do that.

For instance, instead of writing:

import { foo } from 'module';
import { bar } from 'module';

We write:

import { foo, bar } from 'module';

No Mergeable Namespace

We shouldn’t have 2 namespaces with the same name so we don’t have to think about how they’ll be merged together.

Instead, we can just name different namespaces with different names.

For instance, instead of writing:

namespace Animals {
  export class Dog {}
}

namespace Animals {
  export interface Cat {
    numberOfLegs: number;
  }
  export class Zebra {}
}

We write:

namespace Pets {
  export class Dog {}
  export interface Cat {
    numberOfLegs: number;
  }
}

name WildAnimals {
  export class Zebra {}
}

No require Imports

Now that ES modules are widely available, we can replace require with import .

For instance, instead of writing:

const { bar } = require('foo');

We can use the ES6 module version of the module and write:

import { bar } from 'foo';

Use const

We should use const instead of let or var whenever we can for declaring variables.

This way, they can’t be accidentally be assigned with another value.

For instance, instead of writing:

var x = 1;
let y = 2;

We write:

const z = 3;

Marking Variables as readonly

We should mark variables as readonly if they’re private and are never modified outside of the constructor.

This way, we won’t be able to accidentally change them.

For instance, instead of writing:

class Foo {
  private bar = 1;
}

We write:

class Foo {
  private readonly bar = 1;
}

Arrow return Shorthand

If we have an arrow function that only has one statement and returns something, we can make them shorter.

Instead of writing:

() => { return x; }

We write:

() => x

Set Array Types

We should set the types of the array content so that we can restrict the types of values that go into the array.

We can also set generic types to make the array generic.

For instance, instead of writing:

const arr = ['foo', 'bar', 1];

We write:

const arr: string[] = ['foo', 'bar', 'baz'];

Callable Types

If we have an interface or a literal type with just a call signature, then we should write it as a function type.

For instance, instead of writing:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

We write:

(source: string, subString: string) => boolean

Conclusion

We should use function types instead of interfaces or literal types with just a function signature.

Arrays should have a type so we can’t put any type of data in it.

We should reduce the complexity of each file.

isNaN should be used for checking for NaN .