Categories
Vuetify

Vuetify — Customize Slider

Vuetify is a popular UI framework for Vue apps.

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

Disabled Slider

We can disable the slider with the disabled prop:

<template>  
  <v-container>  
    <v-row>  
      <v-col col="12">  
        <v-slider disabled label="Disabled" value="30"></v-slider>  
      </v-col>  
    </v-row>  
  </v-container>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({}),  
};  
</script>

Readonly Slider

A slider can also be disabled with the readonly prop. The difference between disabled and readonly is that a read-only slider looks a regular one.

For example, we can write:

<template>  
  <v-container>  
    <v-row>  
      <v-col col="12">  
        <v-slider readonly label="Disabled" value="30"></v-slider>  
      </v-col>  
    </v-row>  
  </v-container>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({}),  
};  
</script>

Icons

Icons can be added to a slider.

For example, we can write:

<template>  
  <v-container>  
    <v-row>  
      <v-col col="12">  
        <v-slider v-model="sound" prepend-icon="mdi-plus"></v-slider>  
      </v-col>  
    </v-row>  
  </v-container>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    sound: 0,  
  }),  
};  
</script>

to add an icon to the left of the slider with the prepend-icon prop.

We can do the same with the append-icon prop to add the icon to the right.

Also, we can listen to clicks on icons with the @click:append and @click:prepend directives.

For example, we can write:

<template>  
  <v-container>  
    <v-row>  
      <v-col col="12">  
        <v-slider  
          v-model="zoom"  
          prepend-icon="mdi-minus"  
          append-icon="mdi-plus"  
          @click:append="zoomIn"  
          @click:prepend="zoomOut"  
        ></v-slider>  
      </v-col>  
    </v-row>  
  </v-container>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    zoom: 100,  
  }),  
  methods: {  
    zoomOut() {  
      this.zoom = this.zoom - 10 || 0;  
    },  
    zoomIn() {  
      this.zoom = this.zoom + 10 || 100;  
    },  
  },  
};  
</script>

We set the zoomIn and zoomOut methods as the values of the directives to change the value of this.zoom and the slider value.

Vertical Sliders

The vertical prop makes a slider display vertically.

For instance, we can write:

<template>  
  <v-container>  
    <v-row>  
      <v-col col="12">  
        <v-slider v-model="value" vertical label="Regular"></v-slider>  
      </v-col>  
    </v-row>  
  </v-container>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    value: 100,  
  }),  
};  
</script>

Thumb

We can display a label for the slider dot.

For instance, we can write:

<template>  
  <v-container>  
    <v-row>  
      <v-col col="12">  
        <v-slider v-model="value" thumb-label="always"></v-slider>  
      </v-col>  
    </v-row>  
  </v-container>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    value: 100,  
  }),  
};  
</script>

The thumb-label prop displays the slider.

We can add a custom label by populating the thumb-label slot:

<template>  
  <v-container>  
    <v-row>  
      <v-col col="12">  
        <v-slider v-model="value" thumb-label="always">  
          <template v-slot:thumb-label="{ value }">{{ value }}</template>  
        </v-slider>  
      </v-col>  
    </v-row>  
  </v-container>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    value: 100,  
  }),  
};  
</script>

Conclusion

We can add sliders to let users set the value to what we want.

Categories
Vuetify

Vuetify — Dividers

Vuetify is a popular UI framework for Vue apps.

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

Dividers

We can use dividers to divide content.

For instance, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-card>
          <v-list two-line>
            <template v-for="(item, index) in items.slice(0, 6)">
              <v-divider v-if="item.divider" :key="index" :inset="item.inset"></v-divider>
              <v-list-item v-else :key="index">
                <v-list-item-avatar>
                  <img :src="item.avatar" />
                </v-list-item-avatar>
                <v-list-item-content>
                  <v-list-item-title v-html="item.title"></v-list-item-title>
                  <v-list-item-subtitle v-html="item.subtitle"></v-list-item-subtitle>
                </v-list-item-content>
              </v-list-item>
            </template>
          </v-list>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    items: [
      {
        avatar: "https://cdn.vuetifyjs.com/images/lists/1.jpg",
        title: "title",
        subtitle: "subtitle",
      },
      { divider: true, inset: true },
      {
        avatar: "https://cdn.vuetifyjs.com/images/lists/2.jpg",
        title: "title",
        subtitle: "subtitle",
      },
      { divider: true, inset: true },
      {
        avatar: "https://cdn.vuetifyjs.com/images/lists/3.jpg",
        title: "title",
        subtitle: "subtitle",
      },
    ],
  }),
};
</script>

We add the v-divider component to divide the rows with the divider.

It’s flush with the v-list-item .

Vertical Dividers

We can add vertical dividers with the vertical prop.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-toolbar color="purple" dark>
          <v-toolbar-title>Title</v-toolbar-title>
          <v-divider class="mx-4" vertical></v-divider>
          <span class="subheading">My Home</span>
          <v-spacer></v-spacer>
          <v-toolbar-items class="hidden-sm-and-down">
            <v-btn text>News</v-btn>
            <v-divider vertical></v-divider>
            <v-btn text>Blog</v-btn>
            <v-divider vertical></v-divider>
            <v-btn text>Music</v-btn>
            <v-divider vertical></v-divider>
          </v-toolbar-items>
          <v-app-bar-nav-icon></v-app-bar-nav-icon>
        </v-toolbar>
      </v-col>
    </v-row>
  </v-container>
</template>

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

We have the v-divider with the vertical prop to add the vertical divider.

It’s also flush with the toolbar.

Vertical Inset Dividers

We can use the inset prop to add margins with the divider:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-toolbar color="purple" dark>
          <v-toolbar-title>Title</v-toolbar-title>
          <v-divider class="mx-4" vertical inset></v-divider>
          <span class="subheading">My Home</span>
          <v-spacer></v-spacer>
          <v-toolbar-items class="hidden-sm-and-down">
            <v-btn text>News</v-btn>
            <v-divider vertical inset></v-divider>
            <v-btn text>Blog</v-btn>
            <v-divider vertical inset></v-divider>
            <v-btn text>Music</v-btn>
            <v-divider vertical inset></v-divider>
          </v-toolbar-items>
          <v-app-bar-nav-icon></v-app-bar-nav-icon>
        </v-toolbar>
      </v-col>
    </v-row>
  </v-container>
</template>

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

Conclusion

We can add dividers for menu items and rows.

Categories
TypeScript

TypeScript any and unknown Types

In TypeScript, there’re 2 data types that can hold anything.

They’re the any and unknown types.

Since they have different names, they’re different.

In this article, we’ll look at the difference between them and what can we do with them.

The any Type

The any type variable lets us assign anything to it.

If it’s used as a parameter, then we can pass in anything.

For example, we can write:

function func(value: any) {
  const foo = 5 * value;
  const bar = value[1];
}

The TypeScript compiler doesn’t restrict what we can do with a variable or parameter that has an any type.

If we have a variable, we can assign anything to it.

For instance, we can write:

let bar: any;

bar = null;
bar = true;
bar = {};

We can assign anything to a variable with the any type.

Also, we can assign an any variable to variables, with any type:

function func(baz: any) {
  const a: null = baz;
  const b: boolean = baz;
  const c: object = baz;
}

A real example would be JSON.parse . Its signature in TypeScript’s type definition is:

JSON.parse(text: string): any;

The unknown type doesn’t exist yet in TypeScript with it’s added to the type definition, so the any type is used as the return type.

The unknown type is a better alternative to any for typing things that don’t have a known structure.

The unknown Type

The unknown type is a safer version of any .

This is because any lets us do anything, but unknown has more restrictions.

Before we can do anything with a value with an unknown type, we’ve to make the type known first by using type assertions, equality, type guards, or assertion functions.

To add type assertions, we can use the as keyword.

For example, we can write:

function func(value: unknown) {
  return (value as number).toFixed(2);
}

Since we casted the value parameter to a number with as , we can call the toFixed method with it to round the number.

The TypeScript compiler can also determine the data type with equality comparison.

For example, we can write:

function func(value: unknown) {
  if (value === 123)
    const rounded =  value.toFixed(2);
  }
}

We check if value is 123.

This way, if it is, then the TypeScript compiler knows it’s a number.

So we can call toFixed on it.

We can do the same with type guards.

To use them, we use the typeof operator to check the type.

So we can write:

function func(value: unknown) {
  if (typeof value === 'number')
    const rounded =  value.toFixed(2);
  }
}

and the compiler will also know that value is a number.

We can also use assertion functions to do the same thing.

For example, we can write:

function func(value: unknown) {
  assertNum(value);
  const rounded = value.toFixed(2);
}

function assertNum(arg: unknown): asserts arg is number {
  if (typeof arg !== 'number') {
    throw new TypeError('not a number');
  }
}

We create a assertNum function to check if arg is a number.

If it’s not, then an exception is thrown.

We then call it in our func function before doing any operation.

This way, the compiler also knows value is a number.

Conclusion

The any type is too flexible for most cases.

unknown type lets us store anything, but we’ve to determine the type of it before doing anything.

Categories
TypeScript

Using TypeScript — Literal Types, Object Types, and Type Aliases

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to define and use literal value types, object types, and type aliases.

Literal Value Types

A literal value type is a data type with a specific set of values and only the ones specified are allowed.

This lets us treat a set of values as a distinct type.

For instance, we can write:

let val: 1 | 2 = 1;

Then we have val which can take on the value 1 or 2.

If we assigned something that we haven’t explicitly allow, then we get:

let val: 1 | 2 = 100;

Then we get the error ‘Type ‘100’ is not assignable to type ‘1 | 2’.ts(2322)‘

Literal Value Types in Functions

We can use literal types in functions.

This lets us restrict a parameter to allow us to set to only several kinds of values.

For instance, we can write:

const totalPrice = (quantity: 1 | 2, price: number) => {
  return quantity * price;
};

Then we can only pass in 1 or 2 for quantity .

Mixing Value Types in a Literal Value Type

We can narrow value types to a literal type.

For instance, we can write:

const randomNum = () => {
  return Math.floor(Math.random() * 3) as 1 | 2 | 3;
};

Then we narrow the type of the returned value of randomNum from number to 1 | 2 | 3 .

randomNum can only return 1, 2, or 3.

Overloads with Literal Value Types

We can use literal types in function overloads.

For instance, we can write:

function foo(val: 1): "foo";
function foo(val: 2): "bar" | "baz";
function foo(val: number): string {
  switch (val) {
    case 1: {
      return "foo";
    }
    case 2: {
      return "bar";
    }
    default: {
      return "baz";
    }
  }
}

Then we can take on various values because of the overloads.

It can return the value based on the value of val that we pass into foo .

Type Aliases

To avoid repeatedly defining the same type, TypeScript has a type alias feature.

We can assign type combinations to an alias so we can use them throughout our code.

For instance, we can write:

type combo = 1 | 2 | 3;

The type operator lets us define a type alias with the name we want.

In our example, our type alias name is combo .

Objects

JavaScript objects are collections of properties that can be created using the literal syntax, or returned by constructors or classes.

Objects can be altered once they’re created.

We can add or remove properties and receive values of different types.

To provide type features for objects, TypeScript lets us specify the structure of an object.

Object Shape Type Annotations

One way to define the shape of an object is to use the object shape type annotation.

We define the property and the type of each property in the object.

For instance, we can write:

const obj: { foo: number } = { foo: 1 };

The code above defined the object obj which can have the property foo which is a number.

Then we assigned something that has such property and value from the right side.

How Shape Types Fit

An object must define all the properties in the shape with the given data type to match the object shape type annotation.

We’ll get an error if the property structure or the data type doesn’t match.

Optional Properties

We can make a property of an object type optional with a ? .

For instance, we can write:

const obj: { foo: number; bar?: sting } = { foo: 1 };

Then bar is optional and we don’t have to add a value to it.

Conclusion

We can define literal types to restrict the value of a parameter or variable to a few values.

Also, we can define object types with properties and types of them.

Finally, we can define type aliases so that we don’t have to repeat the same data type annotation everywhere.

Categories
TypeScript

Using TypeScript — Extending Generic Types

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to extend generic types in TypeScript.

Extending Generic Classes

We can add extra features to existing classes.

For instance, we can write:

class Collection<T extends { name: string }> {
  protected items: T[] = [];
  constructor(items: T[]) {
    this.items.push(...items);
  }
  add(items: T) {
    this.items.push(items);
  }
  remove(index: number) {
    this.items.splice(index, 1);
  }
  getItem(index: number): T {
    return this.items[index];
  }
}

class SearchableCollection<T extends { name: string }> extends Collection<T> {
  find(name: string): T | undefined {
    return this.items.find(item => item.name === name);
  }
}

In the example above, we have the { name: string } type which T must match.

At least we need the name string property.

For instance, we can write:

interface Person {
  name: string;
  age: number;
}

const persons: SearchableCollection<Person> = new SearchableCollection<Person>([
  {
    name: "james",
    age: 1
  }
]);

Person has the name string property and the age number property, but we can still use it.

As long as the object has the name property and its value is a string, we can use it.

Restricting the Generic Type Parameter

We can assign a more restrictive type than the one that’s specified in the type parameter.

For instance, we can write:

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Owner extends Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    super(name);
    this.age = age;
  }
}

class Collection<T extends Person | Owner> {
  private items: T[] = [];
  constructor(items: T[]) {
    this.items.push(...items);
  }

  add(items: T) {
    this.items.push(items);
  }

  remove(index: number) {
    this.items.splice(index, 1);
  }

  getItem(index: number): T {
    return this.items[index];
  }
}

Then we can use it with a more restrictive type by writing:

const items: Collection<Owner> = new Collection<Owner>([new Owner("james", 1)]);

Owner is more restrictive than Person since it’s a subclass of Person .

Now if we try to add a Person instance into the array we pass into the constructor, we’ll get an error.

For example, if we have:

const items: Collection<Owner> = new Collection<Owner>([
  new Owner("james", 1),
  new Person("jane")
]);

We get the error ‘Property ‘age’ is missing in type ‘Person’ but required in type ‘Owner’.‘

Static Method on a Generic Class

We can define a static method in a generic class.

For instance, we can write:

class Collection<T extends Person | Owner> {
  private items: T[] = [];
  constructor(items: T[]) {
    this.items.push(...items);
  }

  add(items: T) {
    this.items.push(items);
  }

  remove(index: number) {
    this.items.splice(index, 1);
  }

  static getItem(items: any[], index: number) {
    return items[index];
  }
}

We used the static keyword to indicate that getItem is a static method. We can’t reference any generic type parameters with static methods. The static method is the share across all instances of a class.

We can getItem by writing:

const item: number = Collection.getItem([1, 2, 3], 1);

If we want to add generic type parameters to a static method, then we’ve to add ones that aren’t used by the class.

For example, we can write:

class Collection<T extends Person | Owner> {
  private items: T[] = [];
  constructor(items: T[]) {
    this.items.push(...items);
  }

  add(items: T) {
    this.items.push(items);
  }

  remove(index: number) {
    this.items.splice(index, 1);
  }

  static getItem<A>(items: A[], index: number) {
    return items[index];
  }
}

We added the A data type placeholder which isn’t used anywhere else.

Then we can call the method by writing:

const item: number = Collection.getItem<number>([1, 2, 3], 1);

Generic Interfaces

We can define generic interfaces.

For instance, we can write:

type Person = {
  name: string;
};

interface Collection<T extends Person> {
  add(...members: T[]): void;
}

Then we can create a class or an object that has the add method that accepts an array with of T type objects that have the members of Person .

Once we have that, we can use the interface as follows:

class DataCollection implements Collection<Person> {
  members: Person[] = [];
  add(...members: Person[]) {
    this.members.push(...members);
  }
}

We set T to Person and then use the Collection interface to enforce our implementation.

Conclusion

We can add generic type parameters to class methods, static methods, and interfaces.

Generic classes can be extended to create subclasses of them, which are also generic.

Likewise, interfaces can also be extended.