Advertisement

#19 Creating A List View Component Using TSX

In this article we will focus on creating a new list view component that we can use when we want to display a list of data in our application. Using a new component will make it easier to make sure that we create a unified look and feel throughout our entire application. Now we could already do that using pug and sass but the component will also allow us to handle behavior. At least for now we will just make sure that our component can respond to the user clicking on an item. We will accomplish all of this in a very general way by using TSX and allowing for a render function to be passed into the list view for any item that we wish to display.

Code Snippets

security-category-model.ts (1:12)

...
export class SecurityCategoryModel {
    ...
    public get abbreviation() {
        const text = { text: "undefined undefined"};
        const territory = (this.territory || text).text.charAt(0);
        const type = (this.type || text).text.charAt(0);
        const segment = (this.segment || text).text
            .split(" ")
            .reduce((prev, cur) => `${prev}${cur.charAt(0)}`, "");
        return `${territory}${type}${segment}`;
    }
    ...
}
root ⟩ src ⟩ store ⟩ security-category-model.ts

ListView.vue (4:11)

<script lang="tsx">
import { Component, Vue } from "vue-property-decorator";

@Component
export default class ListView extends Vue {
    private render() {
        return (
            <ul>
                <li>List View</li>
            </ul>
        );
    }
}
</script>
root ⟩ src ⟩ components ⟩ ListView.vue

Securities.vue (5:01)

<script lang="ts">
...
import ListView from "@/components/ListView.vue";

@Component({
    components: {
        ListView,
    },
})
export default class Securities extends Vue {
    ...
}
</script>
root ⟩ src ⟩ components ⟩ Securities.vue

Securities.vue (5:27)

<template lang="pug">
ListView/
</template>
root ⟩ src ⟩ views ⟩ Securities.vue

ListView.vue (5:42)

<script lang="tsx">
import { ... Prop, ... } from "vue-property-decorator";

@Component
export default class ListView extends Vue {
    @Prop() private readonly items!: any[];
    @Prop() private readonly keyFn!: (item: any) => number;
    @Prop() private readonly onClick!: (item: any) => void;
    @Prop() private readonly renderFn!: (item: any) => JSX.Element;

    private idFn(item: any) {
        return typeof (this.keyFn) !== "undefined"
            ? this.keyFn(item)
            : item.id;
    }

    private render() {
        if (typeof(this.items) === "undefined") {
            return;
        }
        return (
            <ul>
                {this.items.map((x) => {
                    return <li key={this.idFn(x)}>
                        <a class="list-item-content" href="#void" onClick={() => this.onClick(x)}>
                            <div class="list-item-text">{this.renderFn(x)}</div>
                            <span>⟩</span>
                        </a>
                    </li>
                })}
            </ul>
        );
    }
}
</script>
root ⟩ src ⟩ components ⟩ ListView.vue

Securities.vue (11:56)

<script lang="tsx">
...
export default class Securities extends Vue {
    ...
    private onClickCategory(category: SecurityCategoryModel) {
        console.log(category);
    }

    private onClickSecurity(security: SecurityModel) {
        console.log(security);
    }

    private renderFnCategory(category: SecurityCategoryModel) {
        return (
            <div>
                <label>{category.text}</label>
                <small>{category.abbreviation}</small>
            </div>
        );
    }

    private renderFnSecurity(security: SecurityModel) {
        return (
            <div>
                <label>{security.symbol}</label>
                <small>{security.category!.abbreviation}</small>
            </div>
        );
    }
}
</script>
root ⟩ src ⟩ views ⟩ Securities.vue

Securities.vue (14:35)

<template lang="pug">
div
    ListView(
        v-bind:items="securities"
        v-bind:onClick="onClickSecurity"
        v-bind:renderFn="renderFnSecurity")/
    ListView(
        v-bind:items="categories"
        v-bind:onClick="onClickCategory"
        v-bind:renderFn="renderFnCategory")/
</template>
root ⟩ src ⟩ views ⟩ Securities.vue
Advertisement

ListView.vue (16:45)

<style lang="sass" scoped>
@import "../bitters/variables"

.list-item-content
    display: flex
    align-items: center
    padding: 0.75rem 0.5rem
    text-decoration: none
    border-bottom: 1px solid $light-gray

    > *
        pointer-events: none

    .list-item-text
        flex: 1
</style>
root ⟩ src ⟩ components ⟩ ListView.vue

index.ts (18:56)

...
export * from "@/store/security-descriptor";
...
export * from "@/store/security-types";
...
root ⟩ src ⟩ store ⟩ index.ts

Securities.vue (19:31)

<script lang="tsx">
...
import {
    ...
    ISecurityMarketsModelState,
    ...
    SecurityDescriptor,
    ...
    STATE_SECURITY_MARKETS,
    State,
} from "@/store";

enum Descriptors {
    Markets,
}

...
export default class Securities extends Vue {
    ...
    @State(STATE_SECURITY_MARKETS) private readonly marketState!: ISecurityMarketsModelState;

    private readonly descriptorMarkets = Descriptors.Markets;

    private get markets() { return this.marketState.items; }
    ...
    private onClickDescriptorFactory(which: Descriptors) {
        return (descriptor: SecurityDescriptor) => {
            this.onClickDescriptor(which, descriptor)
        }
    }

    private onClickDescriptor(which: Descriptors, descriptor: SecurityDescriptor) {
        console.log(`${Descriptors[which]} ${descriptor.id}`);
    }
    ...
    private renderFnDescriptor(descriptor: SecurityDescriptor) {
        return (
            <div>
                <label>{descriptor.text}</label>
            </div>
        );
    }
    ...
}
</script>
root ⟩ src ⟩ views ⟩ Securities.vue

Securities.vue (24:14)

<template lang="pug">
div
    ...
    ListView(
        v-bind:items="markets"
        v-bind:onClick="onClickDescriptorFactory(descriptorMarkets)"
        v-bind:renderFn="renderFnDescriptor")/
</template>
root ⟩ src ⟩ views ⟩ Securities.vue

Securities.vue (25:02)

<script lang="tsx">
...
import {
    ...
    ISecuritySegmentModelState,
    ISecurityTerritoryModelState,
    ISecurityTypeModelState,
    ...
    STATE_SECURITY_SEGMENTS,
    STATE_SECURITY_TERRITORIES,
    STATE_SECURITY_TYPES,
    ...
} from "@/store";

enum Descriptors {
    ...
    Segments,
    Territories,
    Types,
}
...
export default class Securities extends Vue {
    ...
    @State(STATE_SECURITY_SEGMENTS) private readonly segmentState!: ISecuritySegmentModelState;
    @State(STATE_SECURITY_TERRITORIES) private readonly territoryState!: ISecurityTerritoryModelState;
    @State(STATE_SECURITY_TYPES) private readonly typeState!: ISecurityTypeModelState;
    ...
    private readonly descriptorSegments = Descriptors.Segments;
    private readonly descriptorTerritories = Descriptors.Territories;
    private readonly descriptorTypes = Descriptors.Types;

    private get segments() { return this.segmentState.items; }
    private get territories() { return this.territoryState.items; }
    private get types() { return this.typeState.items; }
    ...
}
</script>
root ⟩ src ⟩ views ⟩ Securities.vue

Securities.vue (26:00)

<template lang="pug">
div
    ...
    ListView(
        v-bind:items="segments"
        v-bind:onClick="onClickDescriptorFactory(descriptorSegments)"
        v-bind:renderFn="renderFnDescriptor")/
    ListView(
        v-bind:items="territories"
        v-bind:onClick="onClickDescriptorFactory(descriptorTerritories)"
        v-bind:renderFn="renderFnDescriptor")/
    ListView(
        v-bind:items="types"
        v-bind:onClick="onClickDescriptorFactory(descriptorTypes)"
        v-bind:renderFn="renderFnDescriptor")/
</template>
root ⟩ src ⟩ views ⟩ Securities.vue
Exciton Interactive LLC
Advertisement