Giter Club home page Giter Club logo

vuex-map-fields's Introduction

vuex-map-fields

Patreon Donate Build Status Coverage Status GitHub stars

Enable two-way data binding for form fields saved in a Vuex store.

ko-fi

Install

npm install --save vuex-map-fields

Basic example

The following example component shows the most basic usage, for mapping fields to the Vuex store using two-way data binding with v-model, without directly modifying the store itself, but using getter and setter functions internally (as it is described in the official Vuex documentation: Two-way Computed Property).

Store

import Vue from 'vue';
import Vuex from 'vuex';

// Import the `getField` getter and the `updateField`
// mutation function from the `vuex-map-fields` module.
import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    fieldA: '',
    fieldB: '',
  },
  getters: {
    // Add the `getField` getter to the
    // `getters` of your Vuex store instance.
    getField,
  },
  mutations: {
    // Add the `updateField` mutation to the
    // `mutations` of your Vuex store instance.
    updateField,
  },
});

Component

<template>
  <div id="app">
    <input v-model="fieldA">
    <input v-model="fieldB">
  </div>
</template>

<script>
import { mapFields } from 'vuex-map-fields';

export default {
  computed: {
    // The `mapFields` function takes an array of
    // field names and generates corresponding
    // computed properties with getter and setter
    // functions for accessing the Vuex store.
    ...mapFields([
      'fieldA',
      'fieldB',
    ]),
  },
};
</script>

Edit basic example

Nested properties

Oftentimes you want to have nested properties in the Vuex store. vuex-map-fields supports nested data structures by utilizing the object dot string notation.

Store

import Vue from 'vue';
import Vuex from 'vuex';

import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    user: {
      firstName: '',
      lastName: '',
    },
    addresses: [
      {
        town: '',
      },
    ],
  },
  getters: {
    getField,
  },
  mutations: {
    updateField,
  },
});

Component

<template>
  <div id="app">
    <input v-model="firstName">
    <input v-model="lastName">
    <input v-model="town">
  </div>
</template>

<script>
import { mapFields } from 'vuex-map-fields';

export default {
  computed: {
    // When using nested data structures, the string
    // after the last dot (e.g. `firstName`) is used
    // for defining the name of the computed property.
    ...mapFields([
      'user.firstName',
      'user.lastName',
      // It's also possible to access
      // nested properties in arrays.
      'addresses[0].town',
    ]),
  },
};
</script>

Edit nested properties example

Rename properties

Sometimes you might want to give your computed properties different names than what you're using in the Vuex store. Renaming properties is made possible by passing an object of fields to the mapFields function instead of an array.

<template>
  <div id="app">
    <input v-model="userFirstName">
    <input v-model="userLastName">
  </div>
</template>

<script>
import { mapFields } from 'vuex-map-fields';

export default {
  computed: {
    ...mapFields({
      userFirstName: 'user.firstName',
      userLastName: 'user.lastName',
    }),
  },
};
</script>

Edit rename properties example

Custom getters and mutations

By default vuex-map-fields is searching for the given properties starting from the root of your state object. Depending on the size of your application, the state object might become quite big and therefore updating the state starting from the root might become a performance issue. To circumvent such problems, it is possible to create a custom mapFields() function which is configured to access custom mutation and getter functions which don't start from the root of the state object but are accessing a specific point of the state.

Store

import Vue from 'vue';
import Vuex from 'vuex';

import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    user: {
      firstName: '',
      lastName: '',
    },
  },
  getters: {
    // By wrapping the `getField()` function we're
    // able to provide a specific property of the state.
    getUserField(state) {
      return getField(state.user);
    },
  },
  mutations: {
    // Mutating only a specific property of the state
    // can be significantly faster than mutating the
    // whole state every time a field is updated.
    updateUserField(state, field) {
      updateField(state.user, field);
    },
  },
});

Component

<template>
  <div id="app">
    <input v-model="firstName">
    <input v-model="lastName">
  </div>
</template>

<script>
import { createHelpers } from 'vuex-map-fields';

// The getter and mutation types we're providing
// here, must be the same as the function names we've
// used in the store.
const { mapFields } = createHelpers({
  getterType: 'getUserField',
  mutationType: 'updateUserField',
});

export default {
  computed: {
    // Because we're providing the `state.user` property
    // to the getter and mutation functions, we must not
    // use the `user.` prefix when mapping the fields.
    ...mapFields([
      'firstName',
      'lastName',
    ]),
  },
};
</script>

Edit custom getters and mutations example

Vuex modules

Vuex makes it possible to divide the store into modules.

Store

import Vue from 'vue';
import Vuex from 'vuex';

import { createHelpers } from 'vuex-map-fields';

// Because by default, getters and mutations in Vuex
// modules, are globally accessible and not namespaced,
// you most likely want to rename the getter and mutation
// helpers because otherwise you can't reuse them in multiple,
// non namespaced modules.
const { getFooField, updateFooField } = createHelpers({
  getterType: 'getFooField',
  mutationType: 'updateFooField',
});

Vue.use(Vuex);

export default new Vuex.Store({
  // ...
  modules: {
    fooModule: {
      state: {
        foo: '',
      },
      getters: {
        getFooField,
      },
      mutations: {
        updateFooField,
      },
    },
  },
});

Component

<template>
  <div id="app">
    <input v-model="foo">
  </div>
</template>

<script>
import { createHelpers } from 'vuex-map-fields';

// We're using the same getter and mutation types
// as we've used in the store above.
const { mapFields } = createHelpers({
  getterType: 'getFooField',
  mutationType: 'updateFooField',
});

export default {
  computed: {
    ...mapFields(['foo']),
  },
};
</script>

Edit Vuex modules example

Namespaced Vuex modules

By default, mutations and getters inside modules are registered under the global namespace – but you can mark modules as namespaced which prevents naming clashes of mutations and getters between modules.

Store

import Vue from 'vue';
import Vuex from 'vuex';

import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex);

export default new Vuex.Store({
  // ...
  modules: {
    fooModule: {
      namespaced: true,
      state: {
        foo: '',
      },
      getters: {
        getField,
      },
      mutations: {
        updateField,
      },
    },
  },
});

Component

<template>
  <div id="app">
    <input v-model="foo">
  </div>
</template>

<script>
import { createHelpers } from 'vuex-map-fields';

// `fooModule` is the name of the Vuex module.
const { mapFields } = createHelpers({
  getterType: 'fooModule/getField',
  mutationType: 'fooModule/updateField',
});

export default {
  computed: {
    ...mapFields(['foo']),
  },
};
</script>

Edit namespaced Vuex modules example

Or you can pass the module namespace string as the first argument of the mapFields() function.

<template>
  <div id="app">
    <input v-model="foo">
  </div>
</template>

<script>
import { mapFields } from 'vuex-map-fields';

export default {
  computed: {
    // `fooModule` is the name of the Vuex module.
    ...mapFields('fooModule', ['foo']),
  },
};
</script>

Edit namespaced Vuex modules example

Multi-row fields

If you want to build a form which allows the user to enter multiple rows of a specific data type with multiple fields (e.g. multiple addresses) you can use the multi-row field mapping function.

Store

import Vue from 'vue';
import Vuex from 'vuex';

import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex);

export default new Vuex.Store({
  // ...
  state: {
    addresses: [
      {
        zip: '12345',
        town: 'Foo Town',
      },
      {
        zip: '54321',
        town: 'Bar Town',
      },
    ],
  },
  getters: {
    getField,
  },
  mutations: {
    updateField,
  },
});

Component

<template>
  <div id="app">
    <div v-for="address in addresses">
      <label>ZIP <input v-model="address.zip"></label>
      <label>Town <input v-model="address.town"></label>
    </div>
  </div>
</template>

<script>
import { mapMultiRowFields } from 'vuex-map-fields';

export default {
  computed: {
    ...mapMultiRowFields(['addresses']),
  },
};
</script>

Edit multi-row fields example

Upgrade from 0.x.x to 1.x.x

Instead of accessing the state directly, since the 1.0.0 release, in order to enable the ability to implement custom getters and mutations, vuex-map-fields is using a getter function to access the state. This makes it necessary to add a getter function to your Vuex store.

import Vue from 'vue';
import Vuex from 'vuex';

// You now have to also import the `getField()` function.
import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    fieldA: '',
    fieldB: '',
  },
  getters: {
    // Add the `getField` getter to the
    // `getters` of your Vuex store instance.
    getField,
  },
  mutations: {
    updateField,
  },
});

Patreon Sponsors

Spiffy

Become a sponsor and get your logo in this README with a link to your site.

Articles

About

Author

Markus Oberlehner
Website: https://markus.oberlehner.net
Twitter: https://twitter.com/MaOberlehner
PayPal.me: https://paypal.me/maoberlehner
Patreon: https://www.patreon.com/maoberlehner

License

MIT

vuex-map-fields's People

Contributors

dependabot[bot] avatar guilemos avatar maoberlehner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vuex-map-fields's Issues

How to handle array fields in store?

Hello there. Please help me to understand how to handle array fields with this plugin. I have members array in store.
It's easy to map array itself with

computed: {
      ...mapFields([
        'org.members'
      ])
    },

adding new items to the array dynamically with needed fields, like:

    methods: {
      addMember: function(e) {
        e.preventDefault();
        this.$store.state.org.members.push(Vue.util.extend({}, {
          name: '',
          email: ''
        }))
      }
    }

and dynamically showing this data in template with:

        <section
          v-for="member in members"
          :key="member.name"
        >
          <input placeholder="Name" v-model="member.name"
          ></input>
          <input placeholder="Email" v-model="member.email"
          ></input>
        </section>

But it's not working, store is not updating.
Thanks for help.

Passing parameter to createHelpers()

Great library!

Any chance it is possible to pass a parameter to the getter & mutation functions? It would be handy to pass in a value, such as a record id, in a scenario where an item in an array is being edited by a dedicated Vue child component.

const { mapFields } = createHelpers({
getterType: 'fooModule/getField',
mutationType: 'fooModule/updateField',
});

Thanks for your time.

Slow performance alongside Logger plugin

This may or may not be completely unrelated to this library - however it only started occurring once I added the mapFields function to my component.
I added Vuex's Logger plugin to my Vuex store shortly before I found this (awesome!) lib.

When I then started to type in a v-model input field with the value being mapped by the library, the application started to lag dramatically: Typing five letters took the application one minute to update the component:

mutation note/updateField @ 10:53:56.285
mutation note/updateField @ 10:54:05.071
mutation note/updateField @ 10:54:16.510
mutation note/updateField @ 10:54:26.158
mutation note/updateField @ 10:54:35.493

In comparison, without the Logger, it takes less than 100ms.

This is not a big deal, I simply disabled Logger and it seems I'm good now.
Just wanted to let you know that Logger doesn't seem to like vuex-map-fields very much 😛

Example writing tests with map fields

I am using map fields in my vuex store but running the tests gives me the following error:

TypeError: this.$store.getters[getterType] is not a function

Are there any examples with vue-jest tests?

Nuxt.js bug "Do not mutate vuex store..."

"vuex-map-fields": "^1.3.0"
"nuxt": "^1.4.1"
Use Namespaced Vuex modules. Did according to the documentation.

"[vuex] Do not mutate vuex store state outside mutation handlers."

Map multi row fields inside of multi row fields

I have an interesting use case that I'm not sure is possible with vuex map fields.

Assuming a state that looks like so:

shipment: {
    id: 1,
    addresses: [
        {zip: 1000, state: 'NE', residents: [
            {name:'Jim', age:27},
            {name:'Ann', age:30}
        ]},
        {zip: 1000, state: 'NE', residents: [
            {name:'Sue', age:27},
            {name:'Betty', age:30}
        ]},
    ]
}

You can use mapMultiRowFields to map the addresses. However, how would you handle the array of residents inside of the array? Trying to access the resident.name throws a mutation error when in vuex strict mode. Is it possible to mutate a nested array inside of array with 2 way binding?

Nested Properties with Multiple Levels

Hello, @maoberlehner

First of all, thank you for this project, it has really helped me!

But I believe I've found a bug in it, basically it doesn't work with nested properties which have more than one level. Like so:

propA: {
  propB: {
    propC: "something"
  }
}

If I try to use something like:

mapFields(['propA.propB.propC'])

I get an error saying that I can't access propC of undefined.

Computed properties still empty, when state is changed

Not sure it's a vuex-map-fields problem but...
I have a simple form and submit button. I do axios call to send data to the server. The problem is, when I type something into form input, property still empty, when store is up to date.
I'm using chrome plugin to debug values:
111
Here is a store:

222
So when I submit the form it always clear filled values. What's wrong?

Integration with Nuxt.js

Hello - currently working on getting this excellent module to work with Nuxt.js. I'm using Nuxt in SSR mode. When I follow your example here, I get:

module namespace not found in mapState():

and...

this.$store.getters[getterType] is not a function

Pretty sure it's to do with when Nuxt inits the Vuex store.

Any ideas?

Thanks!

mapMultiRowFields nested Array (nested v-for)

Hi. Thanks for the awesome library. I'm using it in my project and I've found this following issue.

My store isn't name spaced. I'm using getField and updateField mutations in a component. I can't seem to update nested array while user is typing the nested text field. But once they input in the parent object, the inner child objects update again.

Here is my store structure.

  people: [
    {
     possessions: [
        {
          price: 100,
          name: 'Test Possession',
        },
        {
          price: 100,
          name: 'Test Possession',
        },
      ],
      name: 'Test Person'
    },
  ],

Here is my component with nested v-for.

<div v-for="person in people">
   <!-- But after this model change updates the nested models in following v-for  -->
  <input type="text" v-model="person.name">
  <div v-for"possession in person.possessions">
   <!-- Every triggers inside this doesn't automatically update store -->
    <input type="text" v-model="possession.name">
    <input type="number" v-model="possession.price">
  </div>
</div>
.
.
import { mapMultiRowFields } from 'vuex-map-fields';
.
.
computed: {
    ...mapMultiRowFields([
      'people',
    ]),
}

I'll make reproduction Fiddle later. 😄 Right now very busy!

Nested Properties not working

In my store.js, I have a couple of modules, and a few nested properties in one of the module

const moduleProjectDetails = {
  state: {
    infrastructureClass: '',
    publicServiceClass: '',
    projectUnit: {
      ton: '',
      square_cubic_meter: '',
      kilometer: '',
      mu: '',
    },
  },
  getters: {
    getField,
  },
  mutations: {
    updateField,
  },
}

export default new Vuex.Store({
  modules: {
    basicInput: moduleBasicInput,
    projectDetails: moduleProjectDetails,
  },
})

In my Component.vue, I use mapfields, and call the property with the identifier after the last .

<md-input class="" v-model="infrastructureClass"></md-input>
<md-input class="" v-model="publicServiceClass"></md-input>
<md-input class="" v-model="ton"></md-input>
<md-input class="" v-model="square_cubic_meter"></md-input>
<md-input class="" v-model="kilometer"></md-input>
<md-input class="" v-model="mu"></md-input>

<script>
import Vue from 'vue'
import { mapFields } from 'vuex-map-fields'

export default {
  name: 'ProjectDetails',
  computed: {
    ...mapFields([
      'infrastructureClass',
      'publicServiceClass',
      'projectUnit.ton',
      'projectUnit.square_cubic_meter',
      'projectUnit.kilometer',
      'projectUnit.mu',
    ]),
  },
}
</script>

I see this error message:

[Vue warn]: Error in render: "TypeError: Cannot read property 'ton' of undefined"

found in

---> <ProjectDetails> at src/components/ProjectDetails.vue
       <MdTab> at src/components/MdTabs/MdTab.vue
         <MdContent> at src/components/MdContent/MdContent.vue
           <MdTabs> at src/components/MdTabs/MdTabs.vue
             <App> at src/MainScreen.vue
               <MdContent> at src/components/MdContent/MdContent.vue
                 <MdAppContent> at src/components/MdApp/MdAppContent.vue
                   <MdAppInternalSideDrawer> at src/components/MdApp/MdAppSideDrawer.vue
                     <App> at src/App.vue
                       <Root>

I see the nested properties available in the Vue.js devtools

image

How can I properly access the nested properties?

Environment

Node: v9.11.1
Vue: 2.5.2
vuex-map-fields: 1.1.7

Make it possible to use dynamic field names in mapFields

Hey, thanks for the great library!

I'm wondering if it's possible to use dynamic field names in the mapFields function? For example, I'm keen to use ...mapFields([`pages[${this.pageNumber}].inputs[${this.questionNumber}].value`]) where pageNumber and questionNumber are props passed to the component.

I'm able to use those props in the template of the component so I know they're being passed down, but when I try to use them in mapFields I get Cannot read property 'inputs' of undefined.

This could just be me misunderstanding Vue - apologies if so!

Thanks

Shortcut for multiple object properties

Hey Markus,

would it make sense to allow to do the following?

computed: {
    ...mapFields(["myObject.*"]) // or simply "myObject"
}

Then in the form one can easily use v-model="myObject.title" etc. Right now, I find it a bit cumbersome/not scalable to have to add each field to that array in mapFields.

mapMultiRowFields does something strange with an array?

Hi All,

I liked how-to-structure-a-complex-vuex-store a lot and I'm trying to use the idea's.

Unfortunately it looks like my "vue-tables-2": "^1.4.50" is not working fine together with mapMultiRowFields from "vuex-map-fields": "^1.2.3".

It looks to come down to the code in my form:

      ...mapUsersFields({
        users: 'rows',
        selectedSeqno: 'selectedSeqno',
        currentPage: 'currentPage',
      }),

      ...mapUsersMultiRowFields(
        { multiUsers: 'rows' }
      ),

users is working fine inside the vue-tables-2 but multiUsers is not. v-for..loop on multiUsers is working perfect so it could be that it's a problem with the table.
But I don't know what to say to the vue-tables-2 team what mapUsersMultiRowFields does? Can you elaborate a little bit? In the debugger I noticed something like reactiveGetter & reactiveSetter being added. Is this all there is?

My users.js store code (partly):

export const { mapFields: mapUsersFields } = createHelpers({
	getterType: 'admin/users/getField',
	mutationType: 'admin/users/UpdateField'
});

export const { mapMultiRowFields: mapUsersMultiRowFields } = createHelpers({
	getterType: 'admin/users/GetField',
	mutationType: 'admin/users/UpdateField'
});

The captial GetField is called in the debugger and seems to be working fine.

Hopefully you can point me in the right direction for a solution.

Kind regards,
Henry

updateField doesn't support broken paths

I have to map an object with a two level depth of nested dynamic properties.
i.e. myContainer.$category.$name.value

I am calling commit('updateField', {path:myContainer.${category}.${name}, value:metric.value});
If $category has never been set separately this break with an error 'Cannot set property ... of null'.

This should be an easy fix, to ensure path safety by creating empty objects if the key doesn't exist already.

Simpler namespace support, overloaded behavior of mapFields to match vuex

I am already using this from vuex:
...mapGetters(['myNamespace', ['mygetter1', 'mygetter2'])
...mapGetters(['myNamespace2', ['mygetterX', 'mygetterY'])

so it would seem nice to me to be able to do:
...mapFields(['myNamespace', ['myfield1', 'myfield2'])
...mapFields(['myNamespace2', ['myfieldX', 'myfieldY'])

or am I missing something?

I don't want to have to do what the README says and do this for each module!

const { mapFields } = createHelpers({
  getterType: 'fooModule/getField',
  mutationType: 'fooModule/updateField',
});

Instead, I just wrote this function once, and it lets me do it my nice way above:

function mapNamespacedFields(namespace, fieldsArray) {
  // Consider trimming the '/' off the end of namespace to be safe?
  const { mapFields } = createHelpers({
    getterType: namespace + "/getField",
    mutationType: namespace + "/updateField"
  });
  return mapFields(fieldsArray);
}

To achieve vuex similarity, this could be a private/non-exported function that gets invoked by mapFields if the first parameter is a String.

Thoughts?

mapped fields inside one object

Hello!

Im new to Vue JS and something like state management and currently trying to create a simple app where I am able to create a construction project with some information and send it to a MongoDB with axios on the frontend and express as the backend.

Currently working on the editing existing projects I have a form with 10 editable propertys.

image

And my question is how do I map those fields in my component so I can have one computed object with all my fields in it?

Because currently it looks like this:

image

Do I have to create again new object which contains all the computed propertys or is there a way to map currentSite as one?

Thanks for your help and best regards!

*Edit in the second screenshot, all the propertys are just inside computed

How to specify namespace for mapFields through component props

I want to pass namespace from vuex module with props but it seems to be impossible.

<my-component namespace="ns1" />

// my-component code
export default {
  props: ["namespace"],
  computed: {
    ...mapFields(??this.namespace??, ["attr1", "attr2"])
  }
}

Of course, there is no way to use this in such way so we don't have access to props. How can I specify namespace as prop in such case?

Multiple getterTypes

Hi,

I'm working Vuex modules to state my data.
I store the data in multiple modules to keep my code base nice and clean.

When using vuex-map-fields I have a situation where I'm using data from multiple modules.
There seems to be no method to do this or I am doing it wrong.

Below is my current code;
My component

<template>
    <div class="">
        <input type="text" v-model="no_panels"><br>
        <input type="text" v-model="firstName"><br>
        <router-link to="/step-2">Go to step 2</router-link>
    </div>
</template>

<script>
    import { createHelpers } from 'vuex-map-fields';

    const { mapFields } = createHelpers({
        getterType: [
            'getKitchenField',
            'getApplicantField',
        ],
        mutationType: 'updateKitchenField',
    });

    export default {
        computed: {
            ...mapFields(['no_panels', 'firstName', 'lastName'])
        },
    }
</script>

My store file

import kitchen from './kitchen';
import applicant from "./applicant";

export default {
    modules: {
        kitchen: kitchen,
        applicant: applicant
    },
    strict: false
}

Applicant.js

import { createHelpers } from 'vuex-map-fields';

const { getApplicantField, updateApplicantField } = createHelpers({
    getterType: 'getApplicantField',
    mutationType: 'updateApplicantField',
});

export default {
    state: {
        firstName: '',
        lastName: ''
    },
    getters: {
        getApplicantField
    },
    mutations: {
        updateApplicantField
    }
}

The code above results in the following error:

Error in render: "TypeError: this.$store.getters[getterType] is not a function"

Add functionality to map complex field objects

Make it possible to provide shema with (exported) types for complex mappings -> mapComplexFields().

schema = {
  users: [
    {
      noMapping: anything // or not defined at all
      name: MAP_FIELD, // add setters getters
      addresses: MAP_ARRAY_FIELD, // naming
      stuff: [
        {
          stuff: MAP_FIELD,
          otherStuff: MAP_ARRAY_FIELD
        }
      ],
    },
  ],
}

Add .d.ts for typescript like vuex

Adding a index.d.ts would be great in order to support typescript. I have drafted one already, based on the vuex one:

declare module 'vuex-map-fields' {
import _Vue, { WatchOptions } from "vue";

// augment typings of Vue.js


export  class Store<S> {
  constructor(options: StoreOptions<S>);

  readonly state: S;
  readonly getters: any;

  replaceState(state: S): void;

  dispatch: Dispatch;
  commit: Commit;

  subscribe<P extends MutationPayload>(fn: (mutation: P, state: S) => any): () => void;
  watch<T>(getter: (state: S) => T, cb: (value: T, oldValue: T) => void, options?: WatchOptions): () => void;

  registerModule<T>(path: string, module: Module<T, S>, options?: ModuleOptions): void;
  registerModule<T>(path: string[], module: Module<T, S>, options?: ModuleOptions): void;

  unregisterModule(path: string): void;
  unregisterModule(path: string[]): void;

  hotUpdate(options: {
    actions?: ActionTree<S, S>;
    mutations?: MutationTree<S>;
    getters?: GetterTree<S, S>;
    modules?: ModuleTree<S>;
  }): void;
}

export  function install(Vue: typeof _Vue): void;

export interface Dispatch {
  (type: string, payload?: any, options?: DispatchOptions): Promise<any>;
  <P extends Payload>(payloadWithType: P, options?: DispatchOptions): Promise<any>;
}

export interface Commit {
  (type: string, payload?: any, options?: CommitOptions): void;
  <P extends Payload>(payloadWithType: P, options?: CommitOptions): void;
}

export interface ActionContext<S, R> {
  dispatch: Dispatch;
  commit: Commit;
  state: S;
  getters: any;
  rootState: R;
  rootGetters: any;
}

export interface Payload {
  type: string;
}

export interface MutationPayload extends Payload {
  payload: any;
}

export interface DispatchOptions {
  root?: boolean;
}

export interface CommitOptions {
  silent?: boolean;
  root?: boolean;
}

export interface StoreOptions<S> {
  state?: S;
  getters?: GetterTree<S, S>;
  actions?: ActionTree<S, S>;
  mutations?: MutationTree<S>;
  modules?: ModuleTree<S>;
  plugins?: Plugin<S>[];
  strict?: boolean;
}

type ActionHandler<S, R> = (injectee: ActionContext<S, R>, payload: any) => any;
interface ActionObject<S, R> {
  root?: boolean;
  handler: ActionHandler<S, R>;
}

export type Getter<S, R> = (state: S, getters: any, rootState: R, rootGetters: any) => any;
export type Action<S, R> = ActionHandler<S, R> | ActionObject<S, R>;
export type Mutation<S> = (state: S, payload: any) => any;
export type Plugin<S> = (store: Store<S>) => any;

export interface Module<S, R> {
  namespaced?: boolean;
  state?: S | (() => S);
  getters?: GetterTree<S, R>;
  actions?: ActionTree<S, R>;
  mutations?: MutationTree<S>;
  modules?: ModuleTree<R>;
}

export interface ModuleOptions{
  preserveState?: boolean
}

export interface GetterTree<S, R> {
  [key: string]: Getter<S, R>;
}

export interface ActionTree<S, R> {
  [key: string]: Action<S, R>;
}

export interface MutationTree<S> {
  [key: string]: Mutation<S>;
}

export interface ModuleTree<R> {
  [key: string]: Module<any, R>;
}

 const _default: {
  Store: typeof Store;
  install: typeof install;
}
type Dictionary<T> = { [key: string]: T };
type Computed = () => any;
type MutationMethod = (...args: any[]) => void;
type ActionMethod = (...args: any[]) => Promise<any>;

interface Mapper<R> {
  (map: string[]): Dictionary<R>;
  (map: Dictionary<string>): Dictionary<R>;
}

interface MapperWithNamespace<R> {
  (namespace: string, map: string[]): Dictionary<R>;
  (namespace: string, map: Dictionary<string>): Dictionary<R>;
}

interface FunctionMapper<F, R> {
  (map: Dictionary<(this: typeof _Vue, fn: F, ...args: any[]) => any>): Dictionary<R>;
}

interface FunctionMapperWithNamespace<F, R> {
  (
    namespace: string,
    map: Dictionary<(this: typeof _Vue, fn: F, ...args: any[]) => any>
  ): Dictionary<R>;
}

interface MapperForState {
  <S>(
    map: Dictionary<(this: typeof _Vue, state: S, getters: any) => any>
  ): Dictionary<Computed>;
}

interface MapperForStateWithNamespace {
  <S>(
    namespace: string,
    map: Dictionary<(this: typeof _Vue, state: S, getters: any) => any>
  ): Dictionary<Computed>;
}

interface NamespacedMappers {
  mapState: Mapper<Computed> & MapperForState;
  mapMutations: Mapper<MutationMethod> & FunctionMapper<Commit, MutationMethod>;
  mapFields: Mapper<Computed>;
  mapActions: Mapper<ActionMethod> & FunctionMapper<Dispatch, ActionMethod>;
}

export  const mapState: Mapper<Computed>
  & MapperWithNamespace<Computed>
  & MapperForState
  & MapperForStateWithNamespace;

export  const mapMutations: Mapper<MutationMethod>
  & MapperWithNamespace<MutationMethod>
  & FunctionMapper<Commit, MutationMethod>
  & FunctionMapperWithNamespace<Commit, MutationMethod>;

export  const mapGetters: Mapper<Computed>
  & MapperWithNamespace<Computed>;

export  const mapActions: Mapper<ActionMethod>
  & MapperWithNamespace<ActionMethod>
  & FunctionMapper<Dispatch, ActionMethod>
  & FunctionMapperWithNamespace<Dispatch, ActionMethod>;

interface HelperOptions{
  getterType: string;
  mutationType:string
}
export  function createHelpers(helperOptions:HelperOptions): NamespacedMappers;

export default _default;
}

Probably the unused types should be removed. Currently it just enables correct typing with createHelpers

Cannot get 2 way with namespaced module

Hi,

I have store split into the modules.
I have module called 'Registration':

import registration from './registration'

Vue.use(Vuex)
Vue.use(FeathersVuex)

const store = new Vuex.Store({
  modules: {
    login,
    registration
  },
  plugins: [
    service('users'),
    auth({userService: 'users'})
  ],
  strict: process.env.NODE_ENV !== 'production'
})

export default store

registration/index:

import { getField, updateField } from 'vuex-map-fields'
import state from './state'
import * as getters from './getters'
import * as mutations from './mutations'
import * as actions from './actions'

export default {
  namespaced: true,
  state,
  getters: {
    getField,
    ...getters
  },
  mutations: {
    updateField,
    ...mutations
  },
  actions
}

and in my component Vue file:

<script>
import { mapFields } from 'vuex-map-fields'
export default {
  name: 'Register',
  computed: {
    ...mapFields('registration', [
      'email',
      'firstName',
      'lastName',
      'password',
      'passwordRepeat'
    ])
  }
}
</script>

in template I am using Quasar Framework inputs but it is not caused by that as it supports v-model:

 q-input(float-label="Email", v-model="email")

It properly updates the store with input value, but the value is missing as I keep getting:

vue.runtime.esm.js?2b0e:587 [Vue warn]: Missing required prop: "value"
found in
---> <QInput>

Is it some bug or did I do something wrong?

Vue.set

Any chance that you can use Vue.set to update data into the state.

This way dynamic objects would be possible.

Use mapMultiRowFields with custom getters (normalized data)

Hi Markus, great job here - thanks for your work!

I have a normalized data structure like this:

sections: [{id: 1, questions: [1,2,3] }...]
questions: [{id: 1, title: "..."}, {id: 2, title: "..."},...]

Therefore I need a custom getter function like the following:

sectionQuestions: state => sectionId => {
    return state.sections[sectionId].questions.map(questionId => _.find(state.questions, {id: questionId}));
  },

Then I can loop over the questions like that:

<div v-for="section in sections">
        <div v-for="(question, index) in sectionQuestions(section.id)" :key="question.id" :index="index">
               {{ question.title }}
        </div>
</div>

This is without using your module so far. But I need to use it, because I want to have input fields for each question so they can be edited - and then I guess your mapMultiRowFields comes in quite handy.

But now the first step is to get the custom getter working with params. This is my component:

import { createHelpers, mapMultiRowFields } from "vuex-map-fields";

const { mapFields } = createHelpers({
  getterType: "sectionQuestions"
});

export default {
  ...
 computed: {
    ...mapMultiRowFields("assessment_designer", ["sectionQuestions"])
  }
...
}

When I call app.sectionQuestions(0) then sectionId is questions instead of 0 in the getter function. I don't understand why.

Documentation examples do not work on vuex webpack templates

@maoberlehner , code is amazing, however it take a while to vue newbies (likely users of the module) to go pass the

Syntax Error: Unexpected token (44:16)

  42 |   },
  43 |   computed: {
> 44 |     mapFields (['registrationDetails.emailAddress'])
     |                 ^
  45 |   },

caused by interaction of the code of your examples with current vue out of the box.

mapMultiRowFields and nested row components ?

Hi, congratulations for this amazing work !

I would like to know if we can handle this kind of situation with mapMultiRowFields helper ?

Form.vue

<template>
  <div id="app">
   <Adresse  v-for="address in addresses" :adresss="adress" />
   <button @click="addAddress"
  </div>
</template>

<script>
import { mapMultiRowFields } from 'vuex-map-fields';

export default {
  computed: {
    ...mapMultiRowFields(['addresses']),
  },
 methods: {
  addAddress() {
  // commit new empty address form
  }
 }
};
</script>

Adress.vue

<template>
  <label>ZIP <input v-model="address.zip"></label>
  <label>Town <input v-model="address.town"></label>
  // ... other inputs
</template>

UglifyJs error

webpack imports the module file which uses ES6 features, in some circumstances, this can lead to the following problem when starting the build process: Unexpected token operator «=», expected punc «,» [./node_modules/vuex-map-fields/src/lib/array-to-object.js:1,0]

Temporary workaround: import the dist files explicitly

import { mapFields } from 'vuex-map-fields/dist';

// Instead of:

import { mapFields } from 'vuex-map-fields';

mapMultiRowFields without nested keys

Hey! Thanks for making this library, it's helped me out a lot! One question I have concerns inputs which are arrays but do not have nested values.

Here is a modified example taken from your README:

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    addresses: [''],
    // instead of:
    // addresses: [
    //  {
    //   zip: '',
    //   town: '',
    // }
    //]
  },
  getters: {
    getField,
  },
  mutations: {
    updateField,
    addAddressRow(state) {
      state.addresses.push('');  // instead of: state.addresses.push({zip: '', town: ''})
    },
  },
});

// app.vue
<template>
  <div id="app">
    <div class="row" v-for="(_, index) in addresses">
      <div class="form-element">
        <label>
          Address: <input v-model="addresses[index]">
        </label>
      </div>
    <button class="add-row" @click="addAddressRow">
      Add new address
    </button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex';
import { mapMultiRowFields } from 'vuex-map-fields';

export default {
  computed: mapMultiRowFields(['addresses']),
  methods: mapMutations(['addAddressRow'])
};
</script>

Is something like this currently possible? Thanks :)

Is it possible to access the value of the v-model?

How can I get the value of the v-model so I can take actions depending on the value? It doesn't seem like you can access the v-model value

//TEMPLATE
<pre>{{id}}</pre> <!-- NO VALUE / NOT REACTIVE -->
<input type="text" v-model="id" name="id">	

//COMPONENT
import { createHelpers } from "vuex-map-fields";

const { mapFields } = createHelpers({
  getterType: "applicationModule/getField",
  mutationType: "applicationModule/updateField"
});

@Component({
	computed: {
		...mapFields({
                        `id:` 'newApplication.id',
			name: 'newApplication.name',
			description: 'newApplication.description',
		}),
	}
})

how to use mapMultiRowFields on nested state ?

i use nuxt js, i want to implementation dynamic form like https://markus.oberlehner.net/blog/how-to-handle-multi-row-forms-with-vue-vuex-and-vuex-map-fields/
how to access mapMultiRowFields on nested state store, bookingData.flight.passengers ?
and i got error store.getters[getterType] is not a function

store/bookingData.js

import { getField, updateField } from 'vuex-map-fields'

export const state = () => ({
  flight: {
    contact: {
      title: '',
      fullname: '',
      email: '',
      phone: ''
    },

    passengers: [
      {
        title: '',
        fullname: '',
        birthday: ''
      },
      ...
    ]
  }
})

export const getters = { getField }

export const mutations = {
  updateField
}

component.vue

<template>
.card.shadow.mb-4(v-for="passenger, index in passengers" v-on:key="'passengers'+index")
  input.form-control(
    type="text" required
    v-model="passenger.fullname")
.....

<script>
.....
computed: {
  ...mapMultiRowFields({ passengers: 'bookingData.flight.passengers' })
}
.....

Nested Properties with arrays not working?

Hi @maoberlehner ,

I am trying to use a nested property, but I get this error all the time:

[Vue warn]: Error in render: "TypeError: this.$store.getters[getterType] is not a function"

What I do is the following:

computed: {
    ...mapFields({
        test: "assessment_designer.questions[0].question_type.test"
    })
}

In the view I use it like that: <input type="number" v-model="test" />

This is my data structure in the "assessment_designer"-module:

 questions: [{
    id: 0,
    sectionId: 0,
    title: "",
    question_type: {
      mode: "multiple-choice",
      test: 123
    }
}]

Everything else from your plugin seems to work well though.

Edit: I have also tried it without renaming the properties like this by simply using an array. Same result.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.