vuex-bind-plugin

1.0.5 • Public • Published

vuex-bind-plugin

A vuex plugin that provides a link between outside data and application state. In this context, outside data refers to any data that is not initially available to the application. For example, outside data could be API endpoints, or a WebAssembly binary*.

*Future version

Update your state, let me take care of the rest. - vuex-bind-plugin

Overview

This plugin:

  • Watches your state and updates outputs based on state changes or action triggers
  • Eliminates the need for additional code/logic when adding new sources of data
  • Provides an at-a-glance perspective of which data is coming from where
  • Seperates component and outside data

The link is established by configuring 2 parts:

  1. Endpoints
  2. Bindings

Endpoints define a place where outside data is stored and what parameters are needed to retreive it. An example of a rest endpoint:

export default { 
  get_user: {
    method: "get",
    url: "/users/",
    params: {
      id: Number
    }
    type : String,
  }
};

Bindings define the variables in the application state to store the parameters and resulting data. An example of a rest binding to the previous endpoint:

const bindings = {
  current_user : {
    endpoint  : "get_user",
    bind      : "change",
  }
};

In the resulting configuration, once $store.commit("update_id", 10) is called, $store.state.current_user will be automatically updated with the resource that is returned by GETing /users/?id=10. See Usage Overview for a more in depth example.

Installation

Install with npm

  npm install vuex-bind-plugin

Usage Overview

  1. Define endpoints
// endpoint_config.js

export default {
  posts: {
    endpoint : "/posts",
    type     : Array,
    method   : "get", 
    params   : {
      user_id : Number,
      date    : Date, 
    }
  },
  create_post : {
    endpoint : "/posts",
    type     : Object,
    method   : "post",
    params   : {
      user_id : Number,
      text    : String,
    }
  }
}
  1. Create a store with namespace and bindings information
// user_store.js

export default {
  namespace : "user",
  state     : {...},
  mutations : {...},
  actions   : {...},
  bindings  : {
    posts : {
      bind : "watch",
      time : 30000,
    },
  }
};

  1. Configure the plugin
import Bind from 'vuex-bind-plugin'
import user_store from './user_store.js'
import endpoints from './endpoint_config.js'

...

const vuex_config = {
  plugins   : [new Bind.Plugin({url: "http://myapi", endpoints})],
  modules   : Bind.Modules({
    user_store
  }),
  state     : {...},
  mutations : {...},
  actions   : {...},
}
  1. Use the state variables as normal
<template>
<div>
  <div v-for="post in posts">
    <span> {{ post }} </span>
  </div>
  <input :v-model="user_select" @update="update_user_id(user_select)" />
  <input :v-model="date_select" @update="update_date(date_select)" />
</div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  name: "UserPosts",
  data: () => ({
    user_select : 0,
    date_select : "",
  }),
  mounted: function() {
    this.$store.dispatch("user/start_bind");
  },
  computed: {
    ...mapState("user", ["posts"]),
    ...mapMutations("user", ["update_user_id", "update_date"])
  }
}
</script>

Plugin Components

bind module store

The plugin automatically creates a module store with the namespace "bind". This module is responsible for keeping track of current bindings and the parameters that trigger updates. It does this by maintaining a list of parameters that other pieces of state depend on.

The bind module also holds state variables used by the data source. For instance, the rest datasource adds url and headers state variables to the bind module. To update these values use the update mutations for them: i.e. commit("update_headers", { key : "Authorization", value: "sometoken" }.

The module name can be changed using the namespace plugin configuration value.

Store modules

Store modules refer to the custom module stores that define the state, getters, mutations, actions, and bindings of the application. These are the modules that are passed to Bind.Modules in the Vuex.Store config. When Bind.Modules is called each module store is inspected for bindings and namespace fields and if they have them they are made into Bind.Stores. Otherwise, the module store is imported as-is.

The configuration for Bind.Store is exactly the same as for a regular vuex store, but with two extra required fields:

  • bindings -- binding configuration for the store module. See Binding Configuration
  • namespace -- Namespace of the module. Not to be confused with the namespaced vuex option.

and one optional field:

An error will occur if namespace is not set or if a Bind.Store is initialized before the Bind.Plugin.

It is possible to configure the root store as a Bind.Store by setting the namespace to "":

import Vuex from "vuex"
import Bind from "vuex-bind-plugin"

var rootStore = new Vuex.Store(new Bind.Store({
  namespace : "",   //MUST BE SET
  plugins   : [new Bind.Plugin({ ... })],
  state     : {...},
  mutations : {...},
  actions   : {...},
  bindings  : {...},
  } 
);

A Bind.Store generates state, mutations and actions for each of the given bindings. See Binding Configuration for information on configuring bindings.

Generated state

Endpoint bindings require two pieces of state: the output and the parameters.

The output and parameters are automatically created.

For example using the default naming:

export default {
  state : {
    user_id : 0
  },
  mutations : {
    update_user_id : (state, value) => ... // Required
  },
  bindings : {
    posts : {
      bind      : "once",
      endpoint  : "posts",
      param_map : {
        user_id : 'id',
      },
    }
  }
}

The output can also be changed to modify another state variable by redirecting the output. Use redirect : 'update_other_thing' to commit to the update_other_thing mutation instead of updating the output state variable.

The starting value of outputs and parameters in the state are defined as the default value of their defined type. See Default Type Values.

Generated mutations

Each state variable must be able to be mutated.

When the state variables are generated, an update_ mutation is created as well.

The prefix for generated update mutations can be set by modifying the naming scheme.

Generated actions

Every binding generates an action to be called to load its data.

By default, the load_ action is defined to load "watch" and "once" bindings and trigger_ actions are generated for "trigger" bindings.

A special "start_bind" action is also created to intialize binding and load binding data. Only call this action once.

The name of the start_bind action, as well as the prefixes for the load and trigger actions, can be set by modifying the naming scheme.

Mapping Helper Functions

Helper functions for mapping binding information into vue components are provided to help clarify that you are dealing with bindings. The helper functions allow you to be agnostic about the current naming scheme. Much like the mapState, mapActions, mapMutations, mapGetters helpers of vuex, these helper functions take a namespace and a list of variables to import from the state. Unlike vuex's map helpers, these helpers do not support an object as the variables to import.

Examples:

<script>
import { mapBindings, syncParams, mapTriggerActions } from 'vuex-bind-plugin'

export default {
  name : "Test",
  computed : {
    ...mapBindings("user", ["info", "posts"]),
    ...syncParams("user", ["name", "password"),
  },
  methods : {
    ...mapTriggerActions("user", ["login"]),
  },
}
</script>

mapBindings and mapParams

mapBindings and mapParams are basically equivalent to each other and vuex's mapState. They are included provided to help provide context to the variables and denote how the component intends to use them.

...
  computed : {
    ...mapBindings(["info", "data"]), // maps info and data from the root state
    ...mapParams("user", ["name", "password"]), // maps info and data from the user namespace
  }
  methods : {
    something : function() {
      console.log(this.name);
      console.log(this.password);
      console.log(this.info);
      console.log(this.data);
      ...
    }
  }
...

syncParams

syncParams maps the given parameters state and makes it so that they're value with be pushed back to state.

  computed : {
    ...syncParams("user", ["name", "password"]),
  }
  methods : {
    something : function() {
      this.name = "something else"; // triggers commit("user/name", "something else")
      ...
    }

mapBindingsWithLoading

mapBindingsWithLoading maps bindings with the loading option set to true. This will map the binding as an object with a loading property. A caveat: using this will convert whatever value you have in your state to an object. The created object has the same prototype as the bindings current value, i.e. methods on the current value's type will work. If the binding is a primitive type and you need to just get the value, call my_binding.valueOf.

  computed : {
    ...mapBindingswithLoading("user", ["info"]),
  }
  methods : {
    something : function() {
      if( ! this.info.loading ) {     // loading variable
        console.log(this.info);       // binding value with loading prop
      }
      ...
    }

mapTriggerActions

mapTriggerActions maps trigger bindings as a function that dispatches the bindings trigger.

  methods : {
    ...mapTriggerActions("user", ["login"]),
    something : function() {
      this.login().then( () => console.log("success!") );
      ...
    }
  }

Configuration

Default Configuration

Default values are used extensively in the configuration of bindings and endpoints. The aim of this is to make configuring bindings and endpoints as simple and concise as possible. Keep in mind that most configuration options have default values that are automatically filled by store configuration.

For example:

const plugin = Bind.Plugin({
  ...
  endpoints : {
    users : {
      url    : "/users/",
      method : "get",
      params : {
        group : String,
      },
    }
  }
});

const bindings = {
  users : {
    bind          : "once",
    endpoint      : "users",
  }
}

Could also be written as:

const plugin = Bind.Plugin({...}); // Endpoint moved to bindings

const bindings = {
  users : {
    endpoint : {
      params : {
        group : String,
      },
    },
  }
};

It is up to the user to decide which defaults will be declared implicitly for the sake of configuration clarity.

Plugin Configuration

Defines how the plugin will function.

Plugin configuration defaults:

const plugin = new Bind.Plugin({
  sources           : { ... },  
  endpoints         : {},
  namespace         : "bind",
  naming            : Bind.SnakeCase(),
  strict            : false,
  default_source    : "rest",
  log_blocked_binds : false,
});
Config Key Default Description
sources {...} Configuration for data sources. See Data Sources
endpoints {} Endpoints config. See Endpoint Configuration
namespace "bind" Namespace of the plugin's "bind" store.
naming Bind.SnakeCase() Naming scheme. See Naming
strict false Check types where possible and log them to the console. Use in development only
default_source "rest" The default source to use when the endpoint source can't be inferred. See Inferring Source
log_blocked_binds false Log when a bind was triggered, but not commited because of unset parameters. Use in development only

Data Sources

The plugin does not use any data source by default. You will need to add configuration values to the sources option to enable them. Each data source has it's own keys in the sources option.

Sources keys:

Key Type Data Source Description
url String "rest" the base url to query for data
headers Object "rest" the initial headers for requests to the data
wasm String "wasm" the path to the wasm file to load
custom Object ... See Custom Data Sources

Inlude the url option to configure the rest data source. You may also configure headers for your requests here.

{
  url : "http://api_url", // Base url of rest requests
  headers : some_headers, // Optional request headers
}

Include the wasm option to configure the WebAssembly data source.

{
  wasm : "application.wasm" // url of the wasm file to load
}

For example, the following snippet configures all three:

const plugin = Bind.Plugin({
  sources : {
    url     : "http://my_api",
    wasm    : "functions.wasm",
  }
  ...
});

Custom Data Sources

To create a custom data source include the custom property in the sources plugin option. Each property of the custom option should be a custom data source that extends the DataSource class below.

export class DataSource {
  // module    = (data) => Promise.resolve(data)         -- Must be defined by subclass
  // args      = (state, params, endpoint) => [ params ] -- Must be defined by subclass
  assign    = (response) => response;

  state     = {};
  mutations = {};

  constructor(state) {
    this.state = state;
  }

  apply_defaults(name, endpoint) {
    endpoint.params = endpoint.params? endpoint.params : {};
    endpoint.type = endpoint.type? endpoint.type : Object;
  }
}

The datasource will be initialized by calling the constructor with no arguments.

Example of using sources.custom field:

import Bind, { DataSource } from 'vuex-bind-plugin'

class MySource extends DataSource {
  constructor() {
    super({ custom_state_var : "init_value" });
    ...
  }
  ...
}

const plugin = new Bind.Plugin({
  ...
  sources : {
    url    : "http://myapi",
    custom : {
       mysource : MySource
    }
  }
})

In the above case, the "mysource" would be initialized as new MySource(). Note: custom datasources are not automatically mocked when mock is set to true.

Queries

The data source describes the 3 parts of pulling data from an endpoint:

  1. Creating the arguments to the module -- args
  2. Calling the module -- module
  3. Assigning the data back to state -- assign

module returns a promise that resolves to the data to commit to state. The arguments to module are calculated by calling the datasource's args function.

args parses the bind state, parameters, and endpoint data to create the arguments for the module call. It returns an Array which is used as the arguments to the datasource's module function. The args function takes three arguments in the following order:

  1. state -- The state store of the bind module
  2. params -- The parameters computed from the state
  3. endpoint -- The endpoint definition

assign transforms the data from the module to the format that will be stored in state.

Basically, the data source is called as module(...args(bind_state, params, endpoint))then((data) => commit(output, assign(data))).

State, Mutations, and Defaults

A data source's apply_defaults function is used to apply the default endpoint options to it's endpoints.

The data source's state and mutations properties are added to the bind module's store, so they can be used in the module's queries.

Example
import { DataSource } from 'vuex-bind-plugin'

class WaiterDataSource extends DataSource {
  module = (greeting, person, data) => Promise.resolve(`${greeting} ${person}, ${JSON.stringify(data)}`)

  args   = (bind_state, params, endpoint) => [
    bind_state.greeting,
    endpoint.person
    params,
  ]

  constructor() {
    super({ greeting : "hello" });
    this.mutations.update_greeting = (state, value) => state.greeting = value;
  }

  apply_defaults(name, endpoint) {
    super.apply_defaults(name, endpoint);
    endpoint.person = endpoint.person ?? name;
  }
}

const plugin = new Bind.Plugin({
  sources : {
    custom : { 
      "waiter" : WaiterDataSource 
    },
  },
});

const store_config = {
  namespace : "kitchen",
  bindings : {

    paul : {
      bind     : "change",
      endpoint : {
        source : "waiter", // Must be set because data source is custom
        params : {
          burger : String,
          cheese : Boolean,
        }
      },
    },

    matt : {
      bind     : "change",
      endpoint : {
        source : "waiter",
        person : "matthew",
        params : {
          pizza : String
        }
      }
    }

  }
}

When initialized into a vuex store the above contrived example with the module state:

{
  burger : "veggie",
  cheese : true,
  pizza : "pepperoni"
}

Would set state.paul to 'Hello paul, { burger : "veggie", cheese : true }'

and state.matt to 'Hello matthew, { pizza : "pepperoni" }'.

Then when commit('kitchen/update_pizza', 'cheese') is called, state.matt will be set to Hello matthew, { pizza : "cheese" }.

A less contrived example would be to have a custom RestDataSource that allows different endpoint options. Note that the RestDataSource's module function is axios. The following would allow the endpoint to set additional_data and additional_params on each request.

import { RestDataSource } from 'vuex-bind-plugin'

class CustomRestDataSource extends RestDataSource {
  args = (bind_state, params, endpoint) => {
    let rest_args = super.args(bind_state, params, endpoint);
    rest_args[0].data = { ...rest_args[0].data, ...endpoint.additional_data };
    rest_args[0].params = { ...rest_args[0].params, ...endpoint.additional_params };
    return rest_args;
  }
}

Using Mock Data

Mock data can be built in to your endpoint definitions by including a mock field.

const endpoints = {
  posts: {
    endpoint : "/posts",
    type     : Array,
    method   : "get", 
    params   : {
      user_id : Number,
      date    : Date, 
    },
    mock : ["my first post", "my second post", "my third post" ]
  },
}

To return the mock data instead of querying the data source set mock : true in sources.

import Bind from "vuex-bind-plugin"

const plugin = new Bind.Plugin({
  ...
  sources : { ..., mock : true }
});

By default, this will cause endpoints to return the data in mock instead of requesting resources. If an endpoint does not have a mock field, the default value for the type is used.

If different logic is needed for retrieving mock data, include a transform function in the sources option for the plugin.

import { lookup_mock } from "vuex-bind-plugin"

const endpoints = {
  posts: {
    endpoint : "/posts",
    type     : Array,
    method   : "get", 
    params   : {
      user    : String,
      date    : Date, 
    },
    mock : ({ user }) => ({
      "fred" : ["fred's first post", "fred is da best"],
      "ben"  : ["ben is da benst", "who wants a taco?"],
      "julia": ["can anyone pet sit?", "hey it's julia"]
    }[user])
  },
}

const plugin = new Bind.Plugin({
  endpoints,
  sources : { 
    mock: true ,
    transform : lookup_mock, 
  },
});

lookup_mock is a utility function that passes param data into the mock_data function of your endpoint. So in the above example, a query with user : "fred" would result in ["fred's first post", "fred is da best"] and a query with user : "greg" would return [] (because "greg" is not present).

A custom function may also be used for the transform. The tranform function is passed an object with endpoint, and input_params fields. The endpoint is a reference to the endpoint definition, and input_params are the current parameter values that have been pulled from the state. For example, a tranform to return the calculated params could be written as tranform : ({ input_params }) => input_params.

Naming

The names of generated mutations, and actions are fully configurable. Use the naming option to specify which naming scheme to use. The two included schemes are Bind.SnakeCase and Bind.CamelCase.

The naming scheme only affects the generated portions of the vuex config and not configuration values associated with the plugin.

Access to the current naming scheme is available by using naming.

Why snake_case as default?

Short Answer: It's easier: prefix + '_' + name requires less computation than prefix + name.slice(0,1).toUpperCase() + name.slice(1).

It's also what is used internally in the plugin. Actions, mutations, etc in snake case also separates them from regular functions. It further drives home the idea that we are working with strings with dispatch and commit. That being said, naming conventions are project/personal specific, so the configuration is left to the user.

camelCase

To use camelCase for actions, mutations, etc use the Bind.CamelCase() setting in the naming option:

import Bind from 'vuex-bind-plugin'

const plugin = Bind.Plugin({
  ...
  naming : Bind.CamelCase(),
})

Custom naming scheme

Naming schemes are fully customizable. The schemes use prefixes to distinguish between generated actions. Schemes have the following prefixes available which default to the string of their name (i.e. update defaults to "update"):

  • update -> prefix for the mutations to update values
  • load -> prefix for the actions load in data from endpoints
  • loading -> prefix for the loading state and mutations
  • done -> prefix for the done mutation
  • trigger -> prefix for the trigger actions for trigger bindings

The scheme also defines start which defines the main action name for starting the binding. It defaults to start_bind for snake case or startBind for camel case.

These values can be changed to whatever is desired by modifying the instance in the naming optino:

import Bind from "vuex-bind-plugin"

const naming = Bind.CamelCase();

naming.prefixes.update  = "mutate";
naming.prefixes.loading = "pulling";
naming.prefixes.trigger = "activate";
naming.start = "begin"

const plugin = Bind.Plugin({
  naming,
  ...
});

const bindings = {
/**
  * Generated Mutations:
  *                        CamelCase default
  * - mutateCustom      |  updateCustom
  * - mutateId          |  updateId
  * - pullingCustom     |  loadingCustom
  * - donePullingCustom |  doneLoadingCustom
  *
  * Generated Actions:
  * - activateCustom    |  triggerCustom
  * - begin             |  startBind      
**/
  custom : {
    bind : "trigger",
    loading   : true,
    params    : {
      id : Number
    },
  }
};

Endpoint Configuration

Endpoints define where data will be retreived from.

Endpoints are data source specific, so the endpoint format may differ between different data sources.

Regardless of data source the type and params fields are available on all endpoints:

const endpoints = {
  ENDPOINT_NAME : {
    source  : "",    // Inferred
    type    : Object,
    params  : {},
  }
}
Config Key Default Description
source "" Data source to use. See Inferring Source.
type Object Type of object returned in the responses data
params {} Endpoint parameters. See Endpoint Parameters
transform N/A Function that takes the (data) from the api and transforms it before committing it to state.

Inferring Source

The source of the data for the endpoint is inferred from the options provided for the endpoint and the plugin. If only one source is configured, it infers all endpoints are configured for that one. If multiple sources are configured, parameters use the presence of certain options to decide which defaults to use:

  • the rest data source uses url and method
  • the wasm data source uses func

Note that when custom datasources are configured, the source can not be inferred. In other words, the source must be set on endpoints that use custom datasources.

For example:

const endpoints = {
  posts : { // Infers source : rest
    url : "/posts/",
  },
  users : { // Infers source : rest
    method : "get",
  },
  fract : { // Infers source : wasm
    func : "mandelbrot"
  }
}

Adding an explicit source to your endpoint config improves clarity, but it is not necessary.

If a source can not be inferred, then it will default to "rest". This may be changed by setting the default_source option in Plugin Configuration. This is useful if a project starts with a single source configured, but then requires another.

Inferring the source of an endpoint is important when considering defaults. See Configuration Defaults.

Endpoint Parameters

Endpoint parameters are set as an object in the form:

params : {
  NAME : TYPE_OR_MATCH
}

Where NAME is the name of the parameter that is used in the request and TYPE_OR_MATCHER is a type (Array, String, etc.) or a match.

Typically the type will be one of String, Array, Number or simply Object (which will match just about anything).

The types of these parameters are used to generate default values of parameters when they are stored in the state.

The strict plugin config option may be set to check the values being sent and received are of the specified type. If a variable that does not match a parameter type is provided and the strict option is set a warning will show on the console. This option should only be set in the development environment.

Default Type Values

Default values are retreived from the type given for the parameter or endpoint.

For built in types the default value is Array(),Number(), Object(), etc. This value is inferred to be the parameter in "unset" state, and it is what the state is initialized to when building Bind.Stores.

Parameter Matchers

The plugin comes with utility functions to "match" values to parameters. For ease of understanding they are available in the match object.

The match object provides the following parameter matchers:

import { match } from 'vuex-bind-plugin'
const params = {
  id     : match.PositiveNumber()     // Matches any positive number or 0
  cost   : match.NegativeNumber()     // Matches any negative number or 0
  lat    : match.NumberRange(50,56)   // Matches a number with the given range. Inclusive, 50 and 56 would match.
  coords : match.ArrayLength(2)       // Matches any array with the given length
  dict   : match.ObjectKeys(["key"] ) // Matches any object with the given keys
  extra  : match.All("hello")         // Matches everything. Sets the default to the given value.
  bits   : match.AnythingBut("world") // Matches everything except for the given value
};

To create a custom matcher, create a function that returns an object with three fields: name, is_set and default.

The name is used to identify the matcher. The is_set function is used to determine whether a parameter is set or not. The default value is used as the default value for the parameter.

For example to match a number greater than 10 define:

const greater_than_ten = () => ({
  name    : "Greater than 10",
  is_set  : (value) => value > 10,
  default : 0,
})

And use it in your endpoint parameters like:

const params = {
  value : greater_than_ten,
}

Rest Endpoints

Rest endpoints require extra fields pertaining to rest request configuration:

const endpoints = {
  ENDPOINT_NAME : {
    url     : "/ENDPOINT_NAME/",
    method  : "get",
    headers : undefined,
    type    : Object,
    params  : {},
  }
}
Config Key Default Description
url "/ENDPOINT_NAME/" Endpoint url. Used as url in axios query. May be a function that takes params and returns the resulting url.
method "get" REST method. Used as method in axios query.
headers null Special headers to set for this request. These headers are added to the headers set in the plugin config and then used as headers in axios query.
type Object See General Endpoints
params {} See Endpoint Parameters

Computed url

The url option in the endpoint may be a function. This is for when the url depends on some params.

const endpoints = {
  post : {
    url : (params) => `/posts/${params.id}`, // i.e. '/posts/10'
    params : {
      id : Number
    },
  }
}

Note that the params are still sent in POST data or GET params.

WebAssembly Endpoints

Not implemented yet

const endpoints = {
  ENDPOINT_NAME : {
    func   : "ENDPOINT_NAME",
    order  : [],
    type   : Object,
    params : {},
  }
}
Config Key Default Description
func "ENDPOINT_NAME" WebAssembly function name
order [] Ordering of arguments to the function call
type Object See General Endpoints
params {} See Endpoint Parameters

Binding Configuration

Defines how to store endpoint data.

const bindings = {
  OUTPUT_NAME : {
    endpoint      : OUTPUT_NAME,
    bind          : "once",
    param_map     : {},
    side_effect   : "",
    redirect      : "",
    transform     : null,
    create_params : true,
    loading       : false,
    period        : 0,
  },
});
Config Key Default Description
endpoint OUTPUT_NAME Name of the endpoint to bind to. Should match an entry in endpoint configs. Defaults to the name of the output variable.
bind "once" Binding type. One of "watch", "trigger", "change", or "once". See Binding Types
param_map {} Parameter mapping. Defines which state variables to use as parameters to the api. See Parameter Mapping
side_effect N/A The name of an action to call when REST data is commited to this binding. The action must be within the current namespace.
redirect N/A Redirects the output to another mutation. Instead of updating the data in OUTPUT_NAME, commit the data here.
transform N/A Function that takes the (data) from the api and tranforms it before committing it to state.
create_params true Set to true to automatically create state variables for endpoint parameters or the mappings in param_map.
loading false Set to true to create state variables that track when the data is being loaded
period N/A Time interval in milliseconds to check for new api data. Only used for "watch" bindings

Binding Types

watch

Use when data and input data change.

This binding periodically updates the data from the api. How often this updates can be set with the "period" binding configuratino. When a parameter in the state changes, the plugin automatically tries to pull data again.

trigger

Use when you want to control when data is sent or retrieved.

This binding does not pull or push data until a trigger action is called. The trigger action by default is "trigger_OUTPUT_NAME". Set trigger_prefix in plugin options to change the prefix for the trigger action.

An example of a trigger action would be posting data to the api. In this case, your application would be responsible for populating the parameters state data. When the binding is triggered the parameters' data would be taken from the state and pushed to the api.

once

Use when both data and input data are set values.

This binding only pulls the data once. It is used by the other two actions, either periodically with watch or directly with trigger. This action checks that parameters are set and then makes the request. If any of the needed parameters are absent, no request takes place.

change

Use when data only changes based on parameters.

Much like watch bindings, the parameters to the endpoint are tracked, but the API is not polled. Instead, output data is only updated when input parameters are changed.

Parameter Mapping

By default the state variables are named the same as the endpoint parameters. For example, if the endpoint has params: {id: Number, text: String} then the state will have { id: 0, text: "" }. The param_map option can be used to set different names for state variables than endpoint parameters.

The param_map option is in the format { STATE_VAR: ENDPOINT_PARAM } and any missing parameters default to the endpoint parameter name.

For example, if the endpoint has params: { id: Number, text: String } and the binding has { param_map: { some_id: "id" } } then the state will have { some_id: 0, text: "" }.

If you would not like the parameters to be automatically created set the create_params binding option to false. Note that when create_params is set to false, the update mutations will need to be created to match the current naming scheme.

Computed Parameters

Parameters may be computed from the state of the store module. To compute a parameter from the local state, set the parameter in the param_map as an object with a computed property that is a function.

Note that the name in the binding param_map must match the name in the endpoint params.

const endpoints = { 
  user_nums : {
    method : "get",
    type   : Array,
    params : {
      id   : String,
      nums : String,
    }
  }
},

const bindings = {
  user_nums : {
    param_map : {
      user_id : "id",
      nums    : { computed : (state) => state.sequence.join(',') },
  }
}

export default {
  bindings,
  endpoints,
  state : {
    sequence : [1,2,3,4,5],
    user_id  : 10,
  },
}

The computed function is passed two values: the local module state and the root getters, in that order. To access local getters from the rootGetters use the namespaced name, i.e rootGetters["ns/my_getter"].

In the above example the nums parameter would be computed as '1,2,3,4,5'.

If you want the parameter to still trigger updates you must include a watch property as well. For instance the nums in the binding above would be:

...
      nums    : {
        computed : (state) => state.sequence.join(','),
        watch : "sequence",
      },
...

This would trigger an update when update_sequence is called.

Organizing Endpoints and Bindings

Endpoints may be configured in 3 different places/scopes:

  1. Plugin Config -- Global scope
  2. Store Config -- Local scope
  3. Binding Config -- Anonymous scope

The following three examples are equivalent configurations:

Endpoint in plugin config

import Bind from 'vuex-bind-plugin'

const mymodule = {
  bindings : {
    get_users : {
      endpoint : "users",     // References users in the global scope
      bind     : "once",
    }
  }
}

const store = new Vuex.Store({
  plugins : [new Bind.Plugin({
    source : { url : "http://myapi" },
    endpoints : {
      users : {
        method : "get",
        type   : Array,
        params : {
          group : Number,
        },
      }
    }
  })],
  modules : Bind.modules({
    mymodule,
  })
});

Endpoint in module config

import Bind from 'vuex-bind-plugin'

const mymodule = {
  endpoints : {
    users : {
      method : "get",
      type   : Array,
      params : {
        group : Number,
      },
    }
  },
  bindings : {
    get_users : {
      endpoint : "users",     // References users in the local scope
      bind     : "once",
    }
  }
}

const store = new Vuex.Store({
  plugins : [new Bind.Plugin({
    source : { url : "http://myapi" },
  })],
  modules : Bind.modules({
    mymodule,
  })
});

Endpoint in binding config

import Bind from 'vuex-bind-plugin'

const mymodule = {
  bindings : {
    get_users : {
      bind     : "once",
      endpoint : {           // No reference
        method : "get",
        type   : Array,
        params : {
          group : Number,
        },
      },
    }
  }
}

const store = new Vuex.Store({
  plugins : [new Bind.Plugin({
    source : { url : "http://myapi" },
  })],
  modules : Bind.modules({
    mymodule,
  })
});

Each of these approaches are equally valid, though using one over the other may lead to cleaner lookibg code. A caveat to defining the endpoint in the binding is that it can not be referenced in other bindings. The best way to define endpoints is up to the current application and api.

Readme

Keywords

Package Sidebar

Install

npm i vuex-bind-plugin

Weekly Downloads

0

Version

1.0.5

License

MIT

Unpacked Size

117 kB

Total Files

15

Last publish

Collaborators

  • hppr-dev