Dynamic function for Vuex store module on floating undo bar example

Viewed 1969

Problem abstract

  1. Make the mutation or action of Vuex store module A call the external function. It could be the mutation or action of other Vuex store module (e. g. B).
  2. A must save link to external method (e. g. mutation or action of other Vuex store module B) because it will be invoked indirectly and not immediately.

In below example:

  • A will be RollbackActionFloatingPanelStoreModule
  • B will be OtherStoreModule
  • The external function will be rollbackRemoveItem

Example (when it could be used)

This floating undo panel allows, obviously, undo the user's actions, however the undo method must be implemented. Also, because the undo panel must rollback multiple kinds of user's control actions, each time we need to specify the undo method implementation.

enter image description here

$ref is not good for TypeScript-based Vue, so let $ref solution will be the last hope. Here is I want fully delegate the undo panel state and behavior to Vuex module, so the Vue component will be logicless:

import { Vue, Component } from "vue-property-decorator";
import { getModule } from "vuex-module-decorators";

import template from "./RollbackActionFloatingPanel.pug";
import RollbackActionFloatingPanelStoreModule
    from "@Store/modules/AssociatedWithComponents/RollbackActionFloatingPanel";


@Component({ template })
export default class RollbackActionFloatingPanel extends Vue {
  private readonly relatedStoreModule: RollbackActionFloatingPanelStoreModule =
      getModule(RollbackActionFloatingPanelStoreModule);
}

Component template (pug language; svg icons are encapsuled to pug mixins)

transition(name="rollback_action_floating_panel")
  .RollbackActionFloatingPanel(v-show="relatedStoreModule.displayFlag")
    +Checkmark__Simple__Bold--MaterialDesignIcon.RollbackActionFloatingPanel-CheckmarkIcon
    .RollbackActionFloatingPanel-Text {{ relatedStoreModule.message }}
    button.RollbackActionFloatingPanel-Button(@click="relatedStoreModule.onClickRollbackButton")
      +Undo__Simple--MaterialDesignIcon.ButtonWithPrependedIcon-Icon
      | Undo
    button.RollbackActionFloatingPanel-Button.RollbackActionFloatingPanel-Button__Primary(@click="relatedStoreModule.dismiss")
      +Hide__StrikethroughEye__Filled--MaterialDesignIcon.ButtonWithPrependedIcon-Icon
      | Hide

The vuex store RollbackActionFloatingPanel needs only one public method: displayAndHideALittleLater. It could be called from other store module or Vue component. Logically, we could specify undo method implementation as parameter (displayAndHideALittleLater(payload: { message: string; onClickRollbackButton: Function; })).

Methods onClickRollbackButton and dismiss are being called from template, so TypeScript can not forbid it's invocation in this case even those are not public.

import { VuexModule, Module, Mutation, Action } from "vuex-module-decorators";

import store, { StoreModuleNames } from "@Store/Store";

@Module({
  name: "VUEX_MODULE:UNDO_PANEL",
  store,
  dynamic: true,
  namespaced: true
})
export default class RollbackActionFloatingPanelStoreModule extends VuexModule {

  private static readonly AUTO_HIDE_TIMEOUT__MILLISECONDS: number = 5000;

  private _displayFlag: boolean = false;
  private _message: string = "";

  private _onClickRollbackButton: Function = () => {};


  @Action
  public displayAndHideALittleLater(
      {
        message,
        onClickRollbackButton
      }: {
        message: string;
        onClickRollbackButton: Function;
      }
  ): void {

    this.setMessage(message);
    this.setOnClickRollbackButtonEventHandler(onClickRollbackButton);
    this.display();

    setTimeout(
        (): void => { this.dismiss(); },
        RollbackActionFloatingPanelStoreModule.AUTO_HIDE_TIMEOUT__MILLISECONDS
    );
  }

  @Action
  private onClickRollbackButton(): void {
    this._onClickRollbackButton();
  }

  @Mutation
  private setMessage(message: string): void {
    this._message = message;
  }

  @Mutation
  private setOnClickRollbackButtonEventHandler(newHandler: Function): void {
    this._onClickRollbackButton = newHandler;
  }

  @Mutation
  private display(): void {
    this._displayFlag = true;
  }

  @Mutation
  private dismiss(): void {
    this._displayFlag = false;
    this._onClickRollbackButton = () => {};
  }

  public get displayFlag(): boolean { return this._displayFlag; }
  public get message(): string { return this._message; }
}

The usage from other Vuex module:

import { VuexModule, Module, Mutation, getModule } from "vuex-module-decorators";
import store, { StoreModuleNames } from "@ProjectInitializer:Store/Store";
import RollbackActionFloatingPanelStoreModule
  from "@ProjectInitializer:Store/modules/AssociatedWithComponents/RollbackActionFloatingPanel";


@Module({
  name: "VUEX_MODULE:OTHER"
  store,
  dynamic: true,
  namespaced: true
})
export default class OtherStoreModule extends VuexModule {

  private _selectedItems: Array<Item> = getDefaultItemsFromRepsitory();
  private _lastDeletedItem: Item | null = null;


  @Mutation
  public removeItem(targetItemId: string): void {

    const targetItemSearchingPredicate: (item: Item) => boolean =
        (item: Item): boolean => item.id === targetItemId;

    const targetItem: Item | undefined = this._selectedItems.find(targetItemSearchingPredicate);
    if (typeof targetItem === "undefined") { return; }

    this._lastDeletedItem = targetEntryPointsGroupSettings;
    const indexOfTargetItem: number = this._selectedItems.findIndex(targetItemSearchingPredicate);

    if (indexOfTargetItem !== -1) {
      this._selectedItems.splice(indexOfTargetItem, 1);
    } else {
      return;
    }

    getModule(RollbackActionFloatingPanelStoreModule).displayAndHideALittleLater({
      message: "Item deleted",
      onClickRollbackButton: (): void => { this.rollbackRemoveItem(); }
    });
  }

  @Mutation
  public rollbackRemoveItem(): void {
    if (this._lastDeletedItem === null) { return; }
    this._selectedItems.unshift(this._lastDeletedItem);
    this._lastDeletedItem = null;
  }
}

error

enter image description here

Error: ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() 
or this.someGetter inside an @Action? 
That works only in dynamic modules. 
If not dynamic use this.context.commit("mutationName", payload) and 
 this.context.getters["getterName"]
Error: Could not perform action onClickRollbackButton

occurs. Because all my modules are dynamics, maybe error message is not accurate. In fact, we call mutation rollbackRemoveItem via non-decorated function _onClickRollbackButton via action onClickRollbackButton. I can not avoid _onClickRollbackButton so easily, because I need to store undo method implementation somewhere until dismiss mutation will be executed.

Attempt with static class field

@Module({
  name: "VUEX_MODULE:UNDO_PANEL",
  store,
  dynamic: true,
  namespaced: true
})
export default class RollbackActionFloatingPanelStoreModule extends VuexModule {

  private static readonly AUTO_HIDE_TIMEOUT__MILLISECONDS: number = 5000;

  private _displayFlag: boolean = false;
  private _message: string = "";

  private static _onClickRollbackButton: Function = () => {};


  // ...

  @Action
  private onClickRollbackButton(): void {
    RollbackActionFloatingPanelStoreModule._onClickRollbackButton();
  }

  @Mutation
  private setOnClickRollbackButtonEventHandler(newHandler: Function): void {
    RollbackActionFloatingPanelStoreModule._onClickRollbackButton = newHandler;
  }
}

Same error.


Repro

I'll share the repro as GitHub Repository until this problem will be solved. If you will be kind enough to clone or download this repository, please execute npm i && npm run JavaScriptBuild in the project directory. Once all npm dependencies will be installed, webpack will build the project and you can open the result on http://localhost:8081/

1 Answers

have u implemented the store rollbackRemoveItem?

You could maybe use sth similar to this:

connect() {
  const connection = getModule(Connection, this.$store);
  connection.testAction();
  this.$store.dispatch('Connection/rollbackRemoveItem');
}
Related