Skip to content
51 changes: 47 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ function normalizeNamespace(fn) {
}

export function getField(state) {
return path => path.split(/[.[\]]+/).reduce((prev, key) => prev[key], state);
return path => path.split(/[.[\]]+/).filter(x => x).reduce((prev, key) => prev[key], state);
}

export function updateField(state, { path, value }) {
path.split(/[.[\]]+/).reduce((prev, key, index, array) => {
path.split(/[.[\]]+/).filter(x => x).reduce((prev, key, index, array) => {
if (array.length === index + 1) {
// eslint-disable-next-line no-param-reassign
prev[key] = value;
Expand Down Expand Up @@ -72,12 +72,13 @@ export const mapMultiRowFields = normalizeNamespace((
// eslint-disable-next-line no-param-reassign
entries[key] = {
get() {
const tpath = typeof path === `function` ? path.call(null, this) : path;
const store = this.$store;
const rows = objectEntries(store.getters[getterType](path));
const rows = objectEntries(store.getters[getterType](tpath));

return rows
.map(fieldsObject => Object.keys(fieldsObject[1]).reduce((prev, fieldKey) => {
const fieldPath = `${path}[${fieldsObject[0]}].${fieldKey}`;
const fieldPath = `${tpath}[${fieldsObject[0]}].${fieldKey}`;

return Object.defineProperty(prev, fieldKey, {
get() {
Expand All @@ -95,6 +96,42 @@ export const mapMultiRowFields = normalizeNamespace((
}, {});
});

export const mapRowFields = normalizeNamespace((
namespace,
paths,
getterType,
mutationType,
) => {
const pathsObject = Array.isArray(paths) ? arrayToObject(paths) : paths;
return Object.keys(pathsObject).reduce((entries, key) => {
const path = pathsObject[key];
// eslint-disable-next-line no-param-reassign
entries[key] = {
get() {
const tpath = typeof path === `function` ? path.call(null, this) : path;
const store = this.$store;
const row = store.getters[getterType](tpath);

if (!row) return {};

return Object.keys(row).reduce((prev, fieldKey) => {
const fieldPath = `${tpath}.${fieldKey}`;
return Object.defineProperty(prev, fieldKey, {
get() {
return store.getters[getterType](fieldPath);
},
set(value) {
store.commit(mutationType, { path: fieldPath, value });
},
});
}, {});
},
};

return entries;
}, {});
});

export const createHelpers = ({ getterType, mutationType }) => ({
[getterType]: getField,
[mutationType]: updateField,
Expand All @@ -110,4 +147,10 @@ export const createHelpers = ({ getterType, mutationType }) => ({
getterType,
mutationType,
)),
mapRowFields: normalizeNamespace((namespace, paths) => mapRowFields(
namespace,
paths,
getterType,
mutationType,
)),
});
2 changes: 2 additions & 0 deletions src/lib/array-to-object.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export default function arrayToObject(fields = []) {
return fields.reduce((prev, path) => {
if (typeof path === `object`) return { ...prev, ...path };

const key = path.split(`.`).slice(-1)[0];

if (prev[key]) {
Expand Down
6 changes: 5 additions & 1 deletion test/multi-row.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ describe(`Component initialized with multi row setup.`, () => {
<input v-model="user.name">
<input v-model="user.email">
</div>
<div v-for="user in usersfun">
<input v-model="user.name">
<input v-model="user.email">
</div>
</div>
`,
computed: {
...mapMultiRowFields([`users`]),
...mapMultiRowFields([`users`, { usersfun: () => `users` }]),
},
};

Expand Down
86 changes: 86 additions & 0 deletions test/row.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';

import { createHelpers, getField, updateField } from '../src';

const localVue = createLocalVue();

localVue.use(Vuex);

const { mapRowFields } = createHelpers({
getterType: `getField`,
mutationType: `updateField`,
});

describe(`Component initialized with row setup.`, () => {
let Component;
let store;
let wrapper;

beforeEach(() => {
Component = {
template: `
<div>
<input v-model="userFun.name">
<input v-model="userFun.email">
<pre>
{{user0}}
</pre>
<pre>
{{noUser}}
</pre>
</div>
`,
data() {
return {
ative_room: '0'
};
},
computed: {
...mapRowFields([{userFun: (c) => { return `users[${c.ative_room}]`; }}, {user0: `users[0]`}]),
...mapRowFields({noUser: `users[2]`}),
},
};

store = new Vuex.Store({
state: {
users: [
{
name: `Foo`,
email: `foo@foo.com`,
},
{
name: `Bar`,
email: `bar@bar.com`,
},
],
},
getters: {
getField,
},
mutations: {
updateField,
},
});

wrapper = shallowMount(Component, { localVue, store });
});

test(`It should render the component.`, () => {
expect(wrapper.exists()).toBe(true);
});

test(`It should update field values when the store is updated.`, () => {
store.state.users[0].name = `New Name`;
store.state.users[0].email = `new@email.com`;

expect(wrapper.find(`input`).element.value).toBe(`New Name`);
});

test(`It should update the store when the field values are updated.`, () => {
wrapper.find(`input`).element.value = `New Name`;
wrapper.find(`input`).trigger(`input`);

expect(store.state.users[0].name).toBe(`New Name`);
});
});