Comments (24)
Here an updated version of @wanxe solution mapDynamicField we are using in our project
import arrayToObject from 'vuex-map-fields/src/lib/array-to-object';
/*
Special vuex-map-field like helper to manage fields inside dynamically defined index or object property
Usage :
computed: {
...mapDynamicFields('my/store/module', ['bigArray[].fieldName'], 'indexArray'),
indexArray() {
return 42;
}
}
So you get a fieldName computed property mapped to store my.store.module.bigArray[42].fieldName property
You can use any computed, props, etc... as index property
Fields can also be passed as object to be renamed { myField: 'bigArray[].fieldName' }
*/
export function mapDynamicFields(namespace, fields, indexField) {
const fieldsObject = Array.isArray(fields) ? arrayToObject(fields) : fields;
return Object.keys(fieldsObject).reduce((prev, key) => {
const field = {
get() {
// 'this' refer to vue component
const path = fieldsObject[key].replace('[]', `[${this[indexField]}]`);
return this.$store.getters[`${namespace}/getField`](path);
},
set(value) {
// 'this' refer to vue component
const path = fieldsObject[key].replace('[]', `[${this[indexField]}]`);
this.$store.commit(`${namespace}/updateField`, { path, value });
},
};
prev[key] = field;
return prev;
}, {});
};
from vuex-map-fields.
Hi @maoberlehner,
I just found out, that the context of mapFields has no idea of the component scope. As I am working with a tree of components and I am mapping components to an object in an array, it would be very helpful to composed the mapFields-String dynamically. For now I will use getter and setters and I will tell you, if I have an idea for an implementation.
Beside that, you're package is very helpful!
Moka
from vuex-map-fields.
Hey @rob-wood-browser, unfortunately, this is not possible because this.pageNumber
and this.questionNumber
are not defined in the context of ...mapFields()
.
// This works:
const pageNumber = 1;
const questionNumber = 2;
export default {
// ...
computed: {
...mapFields([`pages[${pageNumber}].inputs[${questionNumber}].value`])
}
// ...
}
// This doesn't work:
export default {
// ...
props: ['pageNumber', 'questionNumber'],
computed: {
// `this` in the context of `mapFields()` does not reference the component.
// Therefore `this.pageNumber` and `this.questionNumber` are undefined!
...mapFields([`pages[${this.pageNumber}].inputs[${this.questionNumber}].value`])
}
// ...
}
I edited your issue title and I'll handle this as a feature request. Although I currently have no idea how I could implement this. Suggestions and / or a pull request are welcome.
from vuex-map-fields.
Sometimes we need to inject the index of the fields to map with vuex, so it can be helpfull. For the moment I'm doing manually with something like this:
vmodel_to_map: {
get () {
return this.$store.state[nameSpace][modelName][this.index].prop_name;
},
set (value) {
this.$store.commit(`${nameSpace}/updateField`, {path: `obj_prop[${this.index}].prop_name`, value});
},
....
},
So if you want to create it dynamically with an index passed as a prop:
const mapDynamicFields = (namespace, model, resource) => {
const result = Object.keys(model).reduce((prev, key) => {
const field = {
get () {
return this.$store.state[namespace][resource][this.index][key];
},
set (value) {
const path = `${resource}[${this.index}].${key}`;
this.$store.commit(`${namespace}/updateField`, {path, value});
},
};
prev[key] = field;
return prev;
}, {});
return result;
};
from vuex-map-fields.
Run into the same problem where I needed a prop to get the id of the field within a list, the solution I came up with is not very elegant, but it gets the job done :
1 _ Define a global variable let self = '';
2 _ Assign self = this
in the beforeCreate
hook
3 _ You have access to the vue instance from the global variable self
(self.propName)
4_ Didn't test it yet with multiple instance of the same component
let self = '';
export default {
props:['item_ID'],
...
computed:{
mapFields('form', [
`list[${slef.item_ID}].name`,
`list[${slef.item_ID}].type`,
`list[${slef.item_ID}].topic`,
`list[${slef.item_ID}].location`,
])
},
...
beforeCreate(){self = this}
}
Hope this helps
PS: Thanks for this great package, really helped me out
from vuex-map-fields.
If anyone is interested this is how I do it
import { mapFields } from 'vuex-map-fields';
export default {
computed: {
supplier(){
return this.get('suppliers').find(supplier => supplier.id === this.supplier_id)
},
...mapFields('supplier', ['name']),
},
}
export const mapFields = function (state, fields) {
return fields.reduce((prev, field) => {
prev[field] = {
get() {
return this[state][field];
},
set(value) {
this[state][field] = value
},
};
return prev;
}, {});
};
from vuex-map-fields.
@AousAnwar that's actually pretty clever 😅
from vuex-map-fields.
Hey @rob-wood-browser, unfortunately, this is not possible because
this.pageNumber
andthis.questionNumber
are not defined in the context of...mapFields()
.// This works: const pageNumber = 1; const questionNumber = 2; export default { // ... computed: { ...mapFields([`pages[${pageNumber}].inputs[${questionNumber}].value`]) } // ... } // This doesn't work: export default { // ... props: ['pageNumber', 'questionNumber'], computed: { // `this` in the context of `mapFields()` does not reference the component. // Therefore `this.pageNumber` and `this.questionNumber` are undefined! ...mapFields([`pages[${this.pageNumber}].inputs[${this.questionNumber}].value`]) } // ... }I edited your issue title and I'll handle this as a feature request. Although I currently have no idea how I could implement this. Suggestions and / or a pull request are welcome.
with this method, what if pageNumber is dynamic ?
like I have button to choose pageNumber (1|2|3...) => choose 3, but the data pages[3] is not loaded
from vuex-map-fields.
Hey @maoberlehner,
Thanks for clarifying this. I'll absolutely have a think about how this feature could be implemented, and equally if you happen to think of a nice solution to my problem that'd be greatly appreciated too!
from vuex-map-fields.
@wanxe Thanks for your ideas. I tried different approaches and just realized that it is better to store in Vuex store all the information required for calculation of the dynamic properties. In my case there is an object received from backend which has the following structure which is saved to the Vuex store:
data: {
series: [
{
name: 'ser1',
y-axis: {
name: 'axis',
enabled: true,
...
}
},
...
]
}
In UI there is a master-detail form with the list of chart series when user can select one series and edit its parameters like name
, enabled
state, etc.
I tried to access series as multi-row fields first but in this case I was unable to use the library to map y-axis
's sub-properties. So I have to write separate computed properties with getters and setters similar to the example above for each y-axis
sub-property.
So I've switched to storing selectedSeriesIndex
in the Vuex's store making the following changes in it:
export default {
namespaced: true,
state: {
widgetEditForm: {
data: {},
selectedSeriesIndex: -1
}
},
getters: {
selectedSeriesIndex(state) {
return state['widgetEditForm'].selectedSeriesIndex
},
selectedSeries(state, getters) {
return (state['widgetEditForm'].data.series && state['widgetEditForm'].data.series[getters.selectedSeriesIndex]) || null
},
getSelectedSeriesField(state, getters) {
if (!getters.selectedSeries) {
return getField(undefined)
}
return getField(getters.selectedSeries)
},
},
mutations: {
updateSelectedSeriesField(state, field) {
let series = state['widgetEditForm'].data.series
let selectedSeriesIndex = state['widgetEditForm'].selectedSeriesIndex
if (!series || selectedSeriesIndex == -1) {
return
}
updateField(series[selectedSeriesIndex], field)
},
}
In *.vue I just map selectedSeries
using the library passing it my getter/mutator. To simplify access to y-axis
sub-properties I also modified the library. See here: #43 (comment)
from vuex-map-fields.
@maoberlehner
I stumbled upon this very issue today.
I personally think the issue is as easy as allowing keys to be a function, which should carry over the closure of the component. (Check this related comment out)
from vuex-map-fields.
@Nicholaiii interesting idea! I'd consider accepting a pull request which adds this feature. Thx!
from vuex-map-fields.
Tested on a multiple instances. It doesn't work :/
Plus, be careful to your slef.item...
instead of self.item...
.
from vuex-map-fields.
I had the same problem here when the inputs' names were like blah[0]
, blah[1]
, etc.
I ended up leaving ...mapFields(['sample', 'a', 'b', 'c'])
for the normal fields, and for my "generated fields" with array names, I made a method.
Normally, for my field like 'sample'
, I would have markup like this:
<span v-show="sample.touched && errors.first('sample')">
{{ errors.first('sample') }}
</span
But it wasn't actually a big deal to fall-back to the old technique that doesn't use mapFields
. I made this method:
isMessageShowing(fieldName) {
if (!this.fields[fieldName]) return false;
return (this.fields[fieldName].touched && this.errors.first(fieldName));
},
and then markup:
<span v-show="isMessageShowing('blah[0]')">
{{ errors.first('blah[0]') }}
</span
The most annoying thing is you can't just use fields.name
in template markup because during initialization, fields.name
is falsy so it will break any code that isn't something like fields.name && fields.name.touched
. That is the main benefit for me to use mapFields
.
But as I just showed, it's not too bad to work around it.
I found this example code here, maybe it's helpful towards augmenting mapFields
, but I'm not sure: https://jsfiddle.net/Linusborg/3p4kpz1t/ I found it by Googling "dynamic computed props". It seems to use a similar strategy but it's incorporating an additional parameter.
Something like that might actually work as a non-breaking change, because mapFields
only has one input parameter. Adding a second parameter for something like "and also map these fields that we don't know the names for until run-time" could be viable.
Sorry I probably can't be of any more help than that; I'd have to spend WAY more time looking at the library code to make a coherent idea!
[edit] upon further inspection, I'm not super confident that example code I linked could get us any farther. The more I look at it, the more I understand why maoberlehner said "I currently have no idea how I could implement this". It's looking like a very tight spot.
from vuex-map-fields.
There's another example in this SO answer for nested fields instead of namespaced:
https://stackoverflow.com/a/55544842
from vuex-map-fields.
Building off @wanxe and @fabien-michel's solutions, I've modified mapMultiRowFields
to a dynamic function as well:
export function mapDynamicMultiRowFields(namespace, paths, indexField) {
const pathsObject = Array.isArray(paths) ? arrayToObject(paths) : paths;
return Object.keys(pathsObject).reduce((entries, key) => {
// eslint-disable-next-line no-param-reassign
entries[key] = {
get() {
const store = this.$store;
const path = pathsObject[key].replace("[]",`[${this[indexField]}]`);
const rows = store.getters[`${namespace}/getField`](path);
return rows.map((fieldsObject, index) =>
Object.keys(fieldsObject).reduce((prev, fieldKey) => {
const fieldPath = `${path}[${index}].${fieldKey}`;
return Object.defineProperty(prev, fieldKey, {
get() {
return store.getters[`${namespace}/getField`](fieldPath);
},
set(value) {
store.commit(`${namespace}/updateField`, {
path: fieldPath,
value
});
}
});
}, {})
);
}
};
return entries;
}, {});
}
Regardless of this issue, thanks for the great library, for my use case, Vuex would be almost worthless if not for your library (I'm building a relatively complicated form builder that has LOTS of v-models that need to bind to the Vuex store). Thanks!
from vuex-map-fields.
@miketimeturner what does this.get('suppliers')
do in context? Could you show your state please? I'm not sure I understand your example.
from vuex-map-fields.
@wdavies973 Could you please show how this would be implemented in a component?
from vuex-map-fields.
@miketimeturner what does
this.get('suppliers')
do in context? Could you show your state please? I'm not sure I understand your example.
That's just a helper function that I created that gets the state "$store.state.suppliers" not really important for this to work. The mapFields function just uses the result to map the properties
from vuex-map-fields.
@geoidesic You can check out my project here: https://github.com/RobluScouting/master-desktop/blob/master/src/components/metrics/RCheckbox.vue
from vuex-map-fields.
@miketimeturner That's just a helper function that I created that gets the state "$store.state.suppliers" not really important for this to work. The mapFields function just uses the result to map the properties
Ok tx. Why do you first import mapFields
, then export your own function with the same name?
from vuex-map-fields.
The mapFields export is what you're importing. (Not this package) Just put it in its own file and import it where you need it. And use it like this...
...mapFields('propertyName', ['fieldOne', 'fieldTwo'])
from vuex-map-fields.
Hi all,
Based on the great solutions provided by @fabien-michel and @wanxe above, hereafter is an extended version that should allow generating getters/setters with nested arrays.
I needed to map a field located at rootArray[].nestedArray[].field
with different indexes. The syntax evolves a little, to provide index of index fields, but I didn't find a more elegant solution yet.
// Index 1 => prop2
// Index 0 => prop1
...mapDynamicFields(namespace, ['rootArray[=1].nestedArray[=0].field'], ['prop1', 'prop2'])
Then, the computed property field
is mapped dynamically to the rootArray[this.prop2].nestedArray[this.prop1].field
Vuex store property. I needed to use an additional character =
, to prevent unexpected replacements during path reduction (see buildFieldPath
function below).
import arrayToObject from 'vuex-map-fields/src/lib/array-to-object';
function buildFieldPath(vm, fieldsObject, field, indexFields) {
if (Array.isArray(indexFields)) {
return indexFields.reduce(
(path, indexField, index) => path.replace(new RegExp(`\\[=${index}\\]`), `[${vm[indexField]}]`),
fieldsObject[field]
);
}
return fieldsObject[field].replace('[]', `[${vm[indexFields]}]`);
}
export function mapDynamicFields(namespace, fields, indexFields) {
const fieldsObject = Array.isArray(fields) ? arrayToObject(fields) : fields;
return Object.keys(fieldsObject).reduce((prev, key) => {
prev[key] = {
get() {
// 'this' refer to vue component
const path = buildFieldPath(this, fieldsObject, key, indexFields);
return this.$store.getters[`${namespace}/getField`](path);
},
set(value) {
// 'this' refer to vue component
const path = buildFieldPath(this, fieldsObject, key, indexFields);
this.$store.commit(`${namespace}/updateField`, { path, value });
}
};
return prev;
}, {});
}
There may be additional features to implement, essentially to provide shortcuts, and a more elegant code may exist. Feel free to comment/improve.
BR
from vuex-map-fields.
#24 (comment) message deleted. please update examples.
from vuex-map-fields.
Related Issues (20)
- How I can handle custom getter / setter for a single property. HOT 1
- createHelper concatenation goes haywire HOT 2
- nested arrays?
- Duplicate field names
- Can we use also actions and not only mutations?
- Support Vuex 4 HOT 3
- [PERFORMANCE] mapMultiRowFields is extremely slow with 100 items HOT 1
- cannot commit from programmatically + new feature action CRUD (nice)
- nuxt-vite support? HOT 2
- Use Vue.set in updateField to support reactive array watching HOT 1
- Namespaced Vuex modules with Folder structure possible?
- ...mapFields conditionally based on a prop value (question)
- mapMultiRowFields doesn't appear to support dot notation? HOT 1
- Pass namespace as a variable to mapFields
- How to handle array(with primitives) fields? - ["one", "two "]
- Reference a specific array index in mapFields?
- How to use ...mapFields in script setup and typescript?
- Something wrong when I use electron-vue HOT 1
- ...mapFields with CYpress componenet testing
- change "Twitter" to "𝕏 (Twitter)"
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from vuex-map-fields.