Skip to content

Instantly share code, notes, and snippets.

@uriannrima
Last active October 9, 2019 18:23
Show Gist options
  • Save uriannrima/d131db13256255d2f07d9e33e7adca63 to your computer and use it in GitHub Desktop.
Save uriannrima/d131db13256255d2f07d9e33e7adca63 to your computer and use it in GitHub Desktop.
Mixin factory to create components with it's own vuex store module.
<template>
<div>
<div class="black-box rounded h-md-up">
<div>
<label>Description</label>
<minimize-button :minimize.sync="minimize"></minimize-button>
</div>
</div>
<div class="description-component" v-show="!minimize">
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="name">
<label>Character Name</label>
<button @click="saveCharacter()">Save</button>
<button @click="newCharacter()">New</button>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="playerName">
<label>Player Name</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="classes">
<label>Class and Level</label>
</div>
<div class="three-part-area">
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="race">
<label>Race</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="alignment">
<label>Alignment</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="deity">
<label>Deity</label>
</div>
</div>
<div class="four-part-area">
<div class="horizontal-container">
<select :value="size.name" class="full-width-input">
<option v-for="(size, index) in allSizes" :key="index" :value="size.name">{{size.name}}</option>
</select>
<label>Size</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="age">
<label>Age</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="gender">
<label>Gender</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="height">
<label>Height</label>
</div>
</div>
<div class="four-part-area">
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="weight">
<label>Weight</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="eyes">
<label>Eyes</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="hair">
<label>Hair</label>
</div>
<div class="horizontal-container">
<input type="text" class="full-width-input" :value="skin">
<label>Skin</label>
</div>
</div>
</div>
</div>
</template>
<script>
import SizeService from 'Services/size.service';
import StoredComponentMixinFactory from 'Store/stored.component.mixin.factory';
import DescriptionStore from './DescriptionStore';
export default {
mixins: [
MinimizableMixin,
StoredComponentMixinFactory({
// Namespacing definition
moduleConfiguration: ['Character', 'Description'],
// The component store.
store: DescriptionStore,
// Map all state properties.
stateMapping: true,
// Map all getters.
gettersMapping: true,
// Map all mutations.
mutationsMapping: true,
// Map all actions.
actionsMapping: true,
// Make a commit informing the module registration.
commitRegistration: true
})
],
data() {
return {
allSizes: []
}
},
created: async function () {
this.allSizes = await SizeService.getAll();
},
methods: {
saveCharacter: async function () {
console.log('Save it');
},
newCharacter: async function () {
console.log('Create new one');
}
}
}
</script>
export default {
namespaced: true,
state: {
name: "Test Name",
playerName: "",
classes: [],
race: "",
alignment: "",
deity: "",
size: {},
age: "",
gender: "",
height: "",
weight: "",
eyes: "",
hair: "",
skin: ""
},
getters: {
classes: (state, getters) => {
return state.classes.map(classe => {
if (!classe.name || !classe.level) return "";
return classe.name + " (" + classe.level + ")";
});
}
},
mutations: {
updateClasses: (state, { classes }) => {
state.classes = [];
const classesValues = classes.split(",");
classesValues.forEach(classesValue => {
// If empty character
if (classesValue.trim() === "") return;
// Clear whitespaces
var classLevel = classesValue.replace(/ /g, "");
// Regex to get level
var levelRegex = /\(([^)]+)\)/;
// Get level data,
var levelData = levelRegex.exec(classLevel);
// Extract level from data
var level = levelData && levelData.length > 1 ? levelData[1] : 1;
// Remove level from classes.
var classOnly = classLevel.replace(levelRegex, "");
state.classes.push({
name: classOnly.trim(),
level: level
});
});
}
}
};
import { createNamespacedHelpers } from 'vuex';
/**
* An mixin factory to configure a component with it's own vuex store module.
* @param {string[]} moduleNamespace - Array containing the "path" to the component.
* @param {Object} module - Component module object.
* @param {string[]|boolean} stateMapping - Array of state properties that you want to mix from the store to the component computed properties. True if you want all state's properties.
* @param {string[]|boolean} gettersMapping - Array of getters methods that you want to mix from the store to the component computed properties. True if you want all methods.
* @param {string[]|boolean} mutationsMapping - Array of mutations methods that you want to mix from the store to the component methods. True if you want all methods.
* @param {string[]|boolean} actionsMapping - Array of actions methods that you want to mix from the store to the component methods. True if you want all methods.
* @param {string|boolean} commitRegistration - Commit name to be executed to inform Vuex that a module has been registered. Helps to make Vue DevTools to know that a new module exists. String containing the commit name, or a boolean to use default 'ModuleRegistered'.
*/
export default function ({ moduleNamespace, module, stateMapping, gettersMapping, mutationsMapping, actionsMapping, commitRegistration } = {}) {
const mixin = {
computed: {},
methods: {},
created() {
// Avoid register module more than once.
// Caveat: If using hot-reload, if you make changes on the script that contains the store
// Or if you create the store in the same script that you define the component, everytime you change it
// The component will register again the store, and it will have it's alreadyRegistered cleared out.
if (module.alreadyRegistered) return;
this.$store.registerModule(moduleNamespace, module);
module.alreadyRegistered = true;
// Since the Vuex Store won't update on Vue DevTools Store Modules until there is a commit...
// After the module has been registered we do a commit (could be a empty commit, but let's be a bit informative)
// After this, Time Travel works.
// Caveat: Again, if you use hot-reload, and the store loses it's information, it will register again and do a new commit.
if (commitRegistration) this.$store.commit((typeof commitRegistration === 'string') ? commitRegistration : 'ModuleRegistered', { moduleNamespace, module, stateMapping, gettersMapping, mutationsMapping, actionsMapping });
}
};
const namespace = (typeof moduleNamespace === 'object') ? moduleNamespace.join('/') : moduleNamespace;
function checkAndMerge(mappingOptions, mappingFunction, fromStore, toComponent) {
if (!fromStore || !toComponent) return;
if (mappingOptions) {
if (typeof mappingOptions === 'boolean') {
toComponent = Object.assign(toComponent, { ...mappingFunction(Object.keys(fromStore)) });
} else {
toComponent = Object.assign(toComponent, { ...mappingFunction(mappingOptions) });
}
}
};
const { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers(namespace);
checkAndMerge(stateMapping, mapState, module.state, mixin.computed);
checkAndMerge(gettersMapping, mapGetters, module.getters, mixin.computed);
checkAndMerge(mutationsMapping, mapMutations, module.mutations, mixin.methods);
checkAndMerge(actionsMapping, mapActions, module.actions, mixin.methods);
return mixin;
};
@uriannrima
Copy link
Author

An small mixin factory to create a mixin that dynamically register a module and map it's state/getters/mutations/actions.
Description.vue (added .js just for better visualization on gist) and DescriptionStore.js are just for reference.

@uriannrima
Copy link
Author

  • Namespaced is required for map* helper functions to work.
  • Flaging the module to avoid duplicate registration.
  • Checking moduleNamespace type as object (array is object and constructor array, too lazy, I'll fix it later).
  • Some renaming.

@uriannrima
Copy link
Author

  • Now using createNamespacedHelpers().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment