Advertisement

#26 Editing Security Models Using Vuex Actions And Mutations

In this article we will add the vuex actions and mutations that we need in order to edit all of our security models and have those changes persisted to our store. Once they are in place we will create a new vue component that we can use to add cancel and save buttons to any details page that we add to our application. These buttons will encapsulate common functionality such as navigating to the previous view when the user presses the save or cancel buttons. We will of course finish by adding these buttons to our security details page.

Code Snippets

store-constants.ts (1:07)

...
export const ACTION_UPDATE_SECURITY = "ACTION_UPDATE_SECURITY";
export const ACTION_UPDATE_SECURITY_CATEGORY = "ACTION_UPDATE_SECURITY_CATEGORY";
export const ACTION_UPDATE_SECURITY_MARKET = "ACTION_UPDATE_SECURITY_MARKET";
export const ACTION_UPDATE_SECURITY_SEGMENT = "ACTION_UPDATE_SECURITY_SEGMENT";
export const ACTION_UPDATE_SECURITY_TERRITORY = "ACTION_UPDATE_SECURITY_TERRITORY";
export const ACTION_UPDATE_SECURITY_TYPE = "ACTION_UPDATE_SECURITY_TYPE";
...
export const MUTATION_UPDATE_SECURITY = "MUTATION_UPDATE_SECURITY";
export const MUTATION_UPDATE_SECURITY_CATEGORY = "MUTATION_UPDATE_SECURITY_CATEGORY";
export const MUTATION_UPDATE_SECURITY_MARKET = "MUTATION_UPDATE_SECURITY_MARKET";
export const MUTATION_UPDATE_SECURITY_SEGMENT = "MUTATION_UPDATE_SECURITY_SEGMENT";
export const MUTATION_UPDATE_SECURITY_TERRITORY = "MUTATION_UPDATE_SECURITY_TERRITORY";
export const MUTATION_UPDATE_SECURITY_TYPE = "MUTATION_UPDATE_SECURITY_TYPE";
src ⟩ store ⟩ store-constants.ts

security-types.ts (2:11)

...
export type PayloadUpdateSecurity = SecurityModel;
export type PayloadUpdateSecurityCategory = SecurityCategoryModel;
export type PayloadUpdateSecurityMarket = SecurityMarketModel;
export type PayloadUpdateSecuritySegment = SecuritySegmentModel;
export type PayloadUpdateSecurityTerritory = SecurityTerritoryModel;
export type PayloadUpdateSecurityType = SecurityTypeModel;
...
src ⟩ store ⟩ security-types.ts

security-module.ts (2:47)

import { Store } from "vuex";
...
import {
    ACTION_UPDATE_SECURITY,
    ACTION_UPDATE_SECURITY_CATEGORY,
    ACTION_UPDATE_SECURITY_MARKET,
    ACTION_UPDATE_SECURITY_SEGMENT,
    ACTION_UPDATE_SECURITY_TERRITORY,
    ACTION_UPDATE_SECURITY_TYPE,
    ...
    MUTATION_UPDATE_SECURITY,
    MUTATION_UPDATE_SECURITY_CATEGORY,
    MUTATION_UPDATE_SECURITY_MARKET,
    MUTATION_UPDATE_SECURITY_SEGMENT,
    MUTATION_UPDATE_SECURITY_TERRITORY,
    MUTATION_UPDATE_SECURITY_TYPE,
    ...
} from "@/store/store-constants";
...
import { ..., StoreActionTree, StoreContext, ..., StoreMutationTree } from "@/store/store-types";
...
import {
    ...
    PayloadUpdateSecurity,
    PayloadUpdateSecurityCategory,
    PayloadUpdateSecurityMarket,
    PayloadUpdateSecuritySegment,
    PayloadUpdateSecurityTerritory,
    PayloadUpdateSecurityType,
} from "@/store/security-types";
...
export const securitiesActions: StoreActionTree = {
    [ACTION_UPDATE_SECURITY](this: Store<IStoreState>, { commit }: StoreContext, payload: PayloadUpdateSecurity) {
        const category = (this.getters as ISecurityGetters)[GETTER_SECURITY_CATEGORY](payload.categoryId);
        const market = (this.getters as ISecurityGetters)[GETTER_SECURITY_MARKET](payload.marketId);

        payload.setCategory(category);
        payload.setMarket(market);
        commit(MUTATION_UPDATE_SECURITY, payload);
    },
    [ACTION_UPDATE_SECURITY_CATEGORY](
        this: Store<IStoreState>,
        { commit }: StoreContext,
        payload: PayloadUpdateSecurityCategory,
    ) {
        const segment = (this.getters as ISecurityGetters)[GETTER_SECURITY_SEGMENT](payload.segmentId);
        const territory = (this.getters as ISecurityGetters)[GETTER_SECURITY_TERRITORY](payload.territoryId);
        const type = (this.getters as ISecurityGetters)[GETTER_SECURITY_TYPE](payload.typeId);

        payload.setSegment(segment);
        payload.setSegment(territory);
        payload.setType(type);
        commit(MUTATION_UPDATE_SECURITY_CATEGORY, payload);
    },
    [ACTION_UPDATE_SECURITY_MARKET](
        this: Store<IStoreState>,
        { commit }: StoreContext,
        payload: PayloadUpdateSecurityMarket,
    ) {
        commit(MUTATION_UPDATE_SECURITY_MARKET, payload);
    },
    [ACTION_UPDATE_SECURITY_SEGMENT](
        this: Store<IStoreState>,
        { commit }: StoreContext,
        payload: PayloadUpdateSecuritySegment,
    ) {
        commit(MUTATION_UPDATE_SECURITY_SEGMENT, payload);
    },
    [ACTION_UPDATE_SECURITY_TERRITORY](
        this: Store<IStoreState>,
        { commit }: StoreContext,
        payload: PayloadUpdateSecurityTerritory,
    ) {
        commit(MUTATION_UPDATE_SECURITY_TERRITORY, payload);
    },
    [ACTION_UPDATE_SECURITY_TYPE](
        this: Store<IStoreState>,
        { commit }: StoreContext,
        payload: PayloadUpdateSecurityType,
    ) {
        commit(MUTATION_UPDATE_SECURITY_TYPE, payload);
    },
};
...
export const securitiesMutations: StoreMutationTree = {
    [MUTATION_UPDATE_SECURITY](state: IStoreState, payload: PayloadUpdateSecurity) {
        const security = findById(state[STATE_SECURITIES], payload.id);

        storeActionValidator
            .begin()
            .while(StoreActions.Updating)
            .throwIf(security)
            .isUndefined(undefinedMessage("security", payload.id, state[STATE_SECURITIES].index));

        security!.categoryId = payload.categoryId;
        security!.last = payload.last;
        security!.marketId = payload.marketId;
        security!.recommendation = payload.recommendation;
        security!.symbol = payload.symbol.toUpperCase();
    },
    [MUTATION_UPDATE_SECURITY_CATEGORY](state: IStoreState, payload: PayloadUpdateSecurityCategory) {
        const category = findById(state[STATE_SECURITY_CATEGORIES], payload.id);

        storeActionValidator
            .begin()
            .while(StoreActions.Updating)
            .throwIf(category)
            .isUndefined(undefinedMessage("category", payload.id, state[STATE_SECURITY_CATEGORIES].index));

        category!.segmentId = payload.segmentId;
        category!.territoryId = payload.territoryId;
        category!.typeId = payload.typeId;
    },
    [MUTATION_UPDATE_SECURITY_MARKET](state: IStoreState, payload: PayloadUpdateSecurityMarket) {
        const market = findById(state[STATE_SECURITY_MARKETS], payload.id);

        storeActionValidator
            .begin()
            .while(StoreActions.Updating)
            .throwIf(market)
            .isUndefined(undefinedMessage("market", payload.id, state[STATE_SECURITY_MARKETS].index));

        market!.text = payload.text.toUpperCase();
    },
    [MUTATION_UPDATE_SECURITY_SEGMENT](state: IStoreState, payload: PayloadUpdateSecuritySegment) {
        const segment = findById(state[STATE_SECURITY_SEGMENTS], payload.id);

        storeActionValidator
            .begin()
            .while(StoreActions.Updating)
            .throwIf(segment)
            .isUndefined(undefinedMessage("segment", payload.id, state[STATE_SECURITY_SEGMENTS].index));

        segment!.text = payload.text;
    },
    [MUTATION_UPDATE_SECURITY_TERRITORY](state: IStoreState, payload: PayloadUpdateSecurityTerritory) {
        const territory = findById(state[STATE_SECURITY_TERRITORIES], payload.id);

        storeActionValidator
            .begin()
            .while(StoreActions.Updating)
            .throwIf(territory)
            .isUndefined(undefinedMessage("territory", payload.id, state[STATE_SECURITY_TERRITORIES].index));

        territory!.text = payload.text;
    },
    [MUTATION_UPDATE_SECURITY_TYPE](state: IStoreState, payload: PayloadUpdateSecurityType) {
        const type = findById(state[STATE_SECURITY_TYPES], payload.id);

        storeActionValidator
            .begin()
            .while(StoreActions.Updating)
            .throwIf(type)
            .isUndefined(undefinedMessage("type", payload.id, state[STATE_SECURITY_TYPES].index));

        type!.text = payload.text;
    },
};
src ⟩ store ⟩ security-module.ts

store.ts (13:37)

...
import { securitiesActions, ..., securitiesMutations, ... } from "@/store/security-module";
...
const actions = {
    ...
    ...securitiesActions,
};
...
const mutations = {
    ...
    ...securitiesMutations,
};
...
src ⟩ store ⟩ store.ts

security-model.ts (14:13)

...
export class SecurityModel {
    ...
    public get category() {
        return this._category;
    }
    public get categoryId() {
        return this._categoryId;
    }
    public set categoryId(id: number) {
        this._categoryId = id;
    }
    public get id() {
        return this._id;
    }
    public set id(id: number) {
        this._id = id;
    }
    public get last() {
        return this._last;
    }
    public set last(last: number) {
        this._last = last;
    }
    public get market() {
        return this._market;
    }
    public get marketId() {
        return this._marketId;
    }
    public set marketId(id: number) {
        this._marketId = id;
    }
    public get recommendation() {
        return this._recommendation;
    }
    public set recommendation(recommendation: SecurityRecommendations) {
        this._recommendation = recommendation;
    }
    public get symbol() {
        return this._symbol;
    }
    public set symbol(symbol: string) {
        this._symbol = symbol;
    }
    ...
}
src ⟩ store ⟩ security-model.ts

Advertisement

security-category-model.ts (14:40)

export class SecurityCategoryModel {
    ...
    public get id() {
        return this._id;
    }
    public set id(id: number) {
        this._id = id;
    }
    public get segment() {
        return this._segment;
    }
    public get segmentId() {
        return this._segmentId;
    }
    public set segmentId(id: number) {
        this._segmentId = id;
    }
    public get territory() {
        return this._territory;
    }
    public get territoryId() {
        return this._territoryId;
    }
    public set territoryId(id: number) {
        this._territoryId = id;
    }
    public get text() {
        const text = { text: "undefined" };
        const segment = (this.segment || text).text;
        const territory = (this.territory || text).text;
        const type = (this.type || text).text;
        return `${territory} ${type} ${segment}`;
    }
    public get type() {
        return this._type;
    }
    public get typeId() {
        return this._typeId;
    }
    public set typeId(id: number) {
        this._typeId = id;
    }
    ...
}
src ⟩ store ⟩ security-category-model.ts

security-descriptors.ts (15:08)

export abstract class SecurityDescriptor {
    ...
    public set text(text: string) {
        this._text = text;
    }

    constructor(..., private _text: string) {}
    ...
}
src ⟩ store ⟩ security-descriptors.ts

SecurityDetails.vue (15:41)

<script lang="ts">
...
import {
    ...
    ACTION_UPDATE_SECURITY,
    ACTION_UPDATE_SECURITY_CATEGORY,
    ACTION_UPDATE_SECURITY_MARKET,
    ACTION_UPDATE_SECURITY_SEGMENT,
    ACTION_UPDATE_SECURITY_TERRITORY,
    ACTION_UPDATE_SECURITY_TYPE,
    ...
    PayloadUpdateSecurity,
    PayloadUpdateSecurityCategory,
    PayloadUpdateSecurityMarket,
    PayloadUpdateSecuritySegment,
    PayloadUpdateSecurityTerritory,
    PayloadUpdateSecurityType,
    ...
    SecurityModel,
    ...
} from "@/store";

type ActionUpdateTerritory = ActionFn<PayloadUpdateSecurityTerritory>;
...
export default class SecurityDetails extends Vue {
    ...
    @Action(ACTION_UPDATE_SECURITY) private readonly updateSecurity!: ActionFn<PayloadUpdateSecurity>;
    @Action(ACTION_UPDATE_SECURITY_CATEGORY) private readonly updateCategory!: ActionFn<PayloadUpdateSecurityCategory>;
    @Action(ACTION_UPDATE_SECURITY_MARKET) private readonly updateMarket!: ActionFn<PayloadUpdateSecurityMarket>;
    @Action(ACTION_UPDATE_SECURITY_SEGMENT) private readonly updateSegment!: ActionFn<PayloadUpdateSecuritySegment>;
    @Action(ACTION_UPDATE_SECURITY_TERRITORY) private readonly updateTerritory!: ActionUpdateTerritory;
    @Action(ACTION_UPDATE_SECURITY_TYPE) private readonly updateType!: ActionFn<PayloadUpdateSecurityType>;
    ...
    private save() {
        switch (this.which) {
            case SecurityDescriptors.Categories:
                const category = new SecurityCategoryModel({
                    id: this.id,
                    segmentId: this.categorySegment!.id,
                    territoryId: this.categoryTerritory!.id,
                    typeId: this.categoryType!.id,
                });
                switch (this.id) {
                    case 0:
                        return;
                    default:
                        this.updateCategory(category);
                        return;
                }
                return;
            case SecurityDescriptors.Markets:
                const market = new SecurityMarketModel(this.id, this.text);
                switch (this.id) {
                    case 0:
                        return;
                    default:
                        this.updateMarket(market);
                        return;
                }
                return;
            case SecurityDescriptors.Securities:
                const security = new SecurityModel({
                    categoryId: this.securityCategory!.id,
                    id: this.id,
                    last: this.securityLast,
                    marketId: this.securityMarket!.id,
                    recommendation: this.securityRecommendation,
                    symbol: this.securitySymbol,
                });
                switch (this.id) {
                    case 0:
                        return;
                    default:
                        this.updateSecurity(security);
                        return;
                }
                return;
            case SecurityDescriptors.Segments:
                const segment = new SecuritySegmentModel(this.id, this.text);
                switch (this.id) {
                    case 0:
                        return;
                    default:
                        this.updateSegment(segment);
                        return;
                }
                return;
            case SecurityDescriptors.Territories:
                const territory = new SecurityTerritoryModel(this.id, this.text);
                switch (this.id) {
                    case 0:
                        return;
                    default:
                        this.updateTerritory(territory);
                        return;
                }
                return;
            case SecurityDescriptors.Types:
                const type = new SecurityTypeModel(this.id, this.text);
                switch (this.id) {
                    case 0:
                        return;
                    default:
                        this.updateType(type);
                        return;
                }
                return;
        }
    }
}
</script>
src ⟩ views ⟩ SecurityDetails.vue

DetailsActionButtons.vue (21:50)

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

import { RoutingService } from "@/components/routing";

@Component
export default class DetailsActionButtons extends Vue {
    @Inject() private readonly routingService!: RoutingService;
    @Prop() private readonly save!: () => void;

    private cancel() {
        this.routingService.back();
    }

    private saveInternal() {
        this.save();
        this.routingService.back();
    }
}
</script>
src ⟩ components ⟩ DetailsActionButtons.vue

DetailsActionButtons.vue (23:15)

<template lang="pug">
div.buttons
    button.red(
        type="button"
        v-on:click.prevent="cancel") Cancel
    button.green(
        type="submit"
        v-on:click.prevent="saveInternal") Save
</template>
src ⟩ components ⟩ DetailsActionButtons.vue

DetailsActionButtons.vue (24:11)

<style lang="sass" scoped>
.buttons
    display: flex

    button
        flex: 1
</style>
src ⟩ components ⟩ DetailsActionButtons.vue

SecuritiesDetails.vue (24:39)

<script lang="ts">
...
import DetailsActionButtons from "@/components/DetailsActionButtons.vue";
...
@Component({
    components: {
        DetailsActionButtons,
    }
})
export default class SecuritiesDetails extends Vue {
    ...
}
</script>
src ⟩ views ⟩ SecuritiesDetails.vue

SecuritiesDetails.vue (25:16)

<template lang="pug">
...
div.securities-details
    form
        ...
        DetailsActionButtons(v-bind:save="save")/
</template>
src ⟩ views ⟩ SecuritiesDetails.vue

_buttons.scss (26:22)

...
@mixin button($color) {
    $hover-color: shade($color, 20%);
    background-color: $color;

    &:hover {
        background-color: $hover-color;
        color: contrast-switch($hover-color);
    }

    &:disabled {
        &:hover {
            background-color: $color;
        }
    }
}

button.red {
    @include button($red);
}

button.green {
    @include button(get-swatch-color($green-swatch, darken-1));
}
src ⟩ bitters ⟩ _buttons.scss

Exciton Interactive LLC
Advertisement