Giter Club home page Giter Club logo

Comments (24)

fabien-michel avatar fabien-michel commented on May 18, 2024 10

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.

badertscher avatar badertscher commented on May 18, 2024 5

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.

maoberlehner avatar maoberlehner commented on May 18, 2024 4

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.

wanxe avatar wanxe commented on May 18, 2024 2

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.

AousAnwar avatar AousAnwar commented on May 18, 2024 2

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.

miketimeturner avatar miketimeturner commented on May 18, 2024 2

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.

maoberlehner avatar maoberlehner commented on May 18, 2024 1

@AousAnwar that's actually pretty clever 😅

from vuex-map-fields.

tonviet712 avatar tonviet712 commented on May 18, 2024 1

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.

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.

rob-wood-browser avatar rob-wood-browser commented on May 18, 2024

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.

anikolaev avatar anikolaev commented on May 18, 2024

@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.

Nicholaiii avatar Nicholaiii commented on May 18, 2024

@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.

maoberlehner avatar maoberlehner commented on May 18, 2024

@Nicholaiii interesting idea! I'd consider accepting a pull request which adds this feature. Thx!

from vuex-map-fields.

spyesx avatar spyesx commented on May 18, 2024

Tested on a multiple instances. It doesn't work :/
Plus, be careful to your slef.item... instead of self.item....

from vuex-map-fields.

agm1984 avatar agm1984 commented on May 18, 2024

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.

joshkpeterson avatar joshkpeterson commented on May 18, 2024

There's another example in this SO answer for nested fields instead of namespaced:
https://stackoverflow.com/a/55544842

from vuex-map-fields.

widavies avatar widavies commented on May 18, 2024

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.

geoidesic avatar geoidesic commented on May 18, 2024

@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.

geoidesic avatar geoidesic commented on May 18, 2024

@wdavies973 Could you please show how this would be implemented in a component?

from vuex-map-fields.

miketimeturner avatar miketimeturner commented on May 18, 2024

@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.

widavies avatar widavies commented on May 18, 2024

@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.

geoidesic avatar geoidesic commented on May 18, 2024

@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.

miketimeturner avatar miketimeturner commented on May 18, 2024

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.

v1nc3n4 avatar v1nc3n4 commented on May 18, 2024

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.

D1mon avatar D1mon commented on May 18, 2024

#24 (comment) message deleted. please update examples.

from vuex-map-fields.

Related Issues (20)

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.