@marshallswain I see you have been busy with more releases just want to say much appreciated!
So..I'm using my version of makeServicePlugin per below. When I 'register' each plugin in the store (see below) I get a property in the store.state with the same 'name' as service. This has been fine but I've been using the same property to store other values. So far no 'collisions' but I see this as way bad. I either need to namespace my stuff into another property say with a leading _ or I need to namespace the feathers vuex service plugin stuff in the .state. The getter/mutations/actions are fine (no conflicts don't really need namespacing).
I guess what I need to do is use modules? They will create some kind of namespace in .state if I understand correctly? I'm not grokking how to do that with fv store plugin. I can't find it in the fv api but it seems as makeServicePlugin has the store as it's only argument https://vuex.vuejs.org/guide/plugins.html. No way to tell it to register as a module under some context. So... can you give me a pointer or two or what you'd consider best practice for isolating that state stuff feather-vuex plugins create from the rest of the store.state? What I'd really like to do is have all my fv plugins use the same context/prop like ._feathers-vuex. I mean like so store._feathers-vuex = {pluginservicename1:{},pluginservicename2:{}....}. Like so would mean 0% chance of namespace collisions.
import fVuex from 'feathers-vuex'
const addFeathers = async (store, db, opts = {}) => {
const { makeServicePlugin, BaseModel } = fVuex(db.api, { serverAlias: opts.alias || 'api' })
function createModelClass (name, Base) {
const nameIt = (name, cls) => ({ [name]: class extends cls {} })[name]
class Model extends Base {
constructor (data, options) {
super(data, options)
this.modelName = name
}
static modelName = name
}
return nameIt(name, Model)
}
// registering plugins dyanmically here
db.collections.forEach(name =>
makeServicePlugin({ Model: createModelClass(name, BaseModel), service: db.api.service(name) })(store)
)
}
export default addFeathers
export { addFeathers }
I forgot I build in a root namespacing argument in my store's base getter/mutuation so for now I have namespaced my stuff away from fv stuff in much the way I described above (everything under one prop). So I'm good for now but really I would like such control of fv allow a root context/namespace for where the plugin stores its values in the state.
I'm not certain I understand what you want to do. Are you trying to put all feathers-vuex state under a specific key in state?
const state = {
feathersVuexStuff: {
service1: {},
service2: {}
}
}
Feathers-Vuex definitely does not offer that as an option.
Feathers-Vuex uses Vuex's plugin feature to dynamically register Vuex modules, under the hood. You can use the namespace option of makeServicePlugin to customize it to whatever you would like.
makeServicePlugin({
service: feathersClient.service('todos'),
namespace: 'whatever-youWantTo-call_it'
})
To be more clear, all plugins registered through feathers-vuex are are namespaced. It applies a convention under the hood to namespace the service as the inflected last portion of the url split by /. You can override that with the namespace option.
Ok duh. I see it now. https://vuex.feathersjs.com/service-plugin.html#configuration
ok using that means I have to change all my calls to mutation/getters
So can I leave the getter/mutation path as is and just change the store state namespace with a combination of namespace and nameStyle? Guess I will try and see
Note that namespace overrides nameStyle.
I recommend using the Modeling API for the majority of your store interactions. Using Models, its fairly rare in my apps to need to access the store from within a component.
I'm pretty biased, but I find that using
Todo.find()
is much nicer than
this.$store.dispatch('todos/find', {})
;)
I'm have been using a mixin to make it easy for those calls and to bind a service/collection context so actually pretty easy for me to bind a namespace context to those functions as well. But maybe instead of mixin I should be using the modeling API? can you bind the api in the mount section like I do with the mixin?
What does "bind the api in the mount section" mean?
One of the things I like about the modeling API is that it allows you to create components where you can pass a Model class as a prop.
<DataTable :model="Todo" />
You also free yourself to work with models that might not even use Vuex under the hood, but you get to keep the same API. One of my plans for Feathers-Vuex is to decouple it from Feathers so that other sources can be used by building plugins that mimic the current Model API.
Testing becomes really easy, too, when you can create a TestModel class that mimics the full functionality. Here's a TestModel that I use in tests and when writing stories:
// import Vue from 'vue'
import { reactive } from '@vue/composition-api'
import objectid from 'objectid'
const defaults = {
idField: '_id'
}
export function makeModel(options) {
const { idField } = Object.assign({}, defaults, options)
const state = reactive({
ids: [],
idField,
keyedById: {},
tempsById: {},
copiesById: {}
})
class TestModel {
constructor(data, options = {}) {
const id = data[idField]
Object.assign(this, data)
if (options.clone) {
if (state.copiesById[id]) {
Object.assign(state.copiesById[id], this)
} else {
state.copiesById[id] = this
}
return state.copiesById[id]
} else if (data.hasOwnProperty(idField)) {
if (state.keyedById[id]) {
Object.assign(state.keyedById[id], this)
} else {
state.keyedById[id] = this
}
return state.keyedById[id]
} else {
Object.defineProperty(this, '__tempId', {
writable: false,
value: objectid(),
iterable: false
})
const tempId = this.__tempId
if (state.tempsById[tempId]) {
Object.assign(state.tempsById[tempId], this)
} else {
state.tempsById[tempId] = this
}
return state.tempsById[id]
}
}
clone() {
const id = getId(this, idField)
const clone = new this.constructor(state.keyedById[id], { clone: true })
return clone
}
commit() {
const id = getId(this, idField)
const clone = new this.constructor(state.copiesById[id])
return clone
}
save() {
if (this.hasOwnProperty(idField)) {
this.patch()
} else {
this.create()
}
}
create() {
if (!this.hasOwnProperty(idField)) {
this[idField] = objectId()
delete state.tempsById[this.__tempId]
delete this.__tempId
}
return new this.constructor(this)
}
update() {
return new this.constructor(this)
}
patch() {
return new this.constructor(this)
}
remove() {
const id = this[idField]
const tempId = this.__tempId
if (state.keyedById.hasOwnProperty(idField)) {
delete state.keyedById[id]
}
if (state.copiesById.hasOwnProperty(idField)) {
delete state.copiesById[id]
}
if (state.tempsById.hasOwnProperty(tempId)) {
delete state.tempsById[tempId]
}
return this
}
}
TestModel.state = state
return TestModel
}
function getId(data, idField) {
const id = data[idField] || data.__tempId
return id
}
Then you can use it like this:
import { makeModel } from './TestModel'
const Todo = makeModel()
// simulate the Model class wherever you want.
After I implement find, get, findInStore and getFromStore methods, I'll be publishing this in the next version of Feathers-Vuex.
The main purpose of the TestModel above, in its current state, was to allow me to model the "clone and commit" API in stories. It has also cut down on the number of custom mutations I need to write.
Lots of APIs out there. Can to specifically point me to some docs/tutorial for the "Modeling" API you mention.
I mean I have generic service component which wraps a generic collection component so to use a specific instance I need to bind the actual service/collection name on the db calls.
Here is my mixin. you see I export a bind method to make this binding easy. BTW Here i could include a namespace arg as well and bind on that by default
e.g. async setDocProp (namespace,collection, id, prop, value) {
If I can better replace this mixin with this 'modeling api' I'm all ears. But since my sevrice component requires a service name property I can't build this "api" beforehand I need to create/bind to a generic api at run time.
// TODO allow passing doc or id
// Any state property used MUSt already be part of collection/service schema
const prop = {
methods: {
async setDocProp (collection, id, prop, value) {
let props = null
if (arguments.length !== 4) {
if (isPlainObj(collection)) {
if (collection.path && (collection.value || collection.props)) {
value = collection.value
props = collection.props
let temp
[temp, id, prop] = collection.path.split('.')
collection = temp
} else ({ collection, id, prop, value, props } = collection)
} else {
if (isPlainObj(id)) props = id
else value = id
let temp
[temp, id, prop] = collection.split('.')
collection = temp
console.log(collection, id, prop)
}
}
if (!props) {
if (isPlainObj(prop)) props = prop
else if (prop && value) props = { [prop]: value }
}
if (collection && id && props) {
// console.log('setting/patching doc prop(s)', collection, id, props)
this.$store.dispatch(`${collection}/patch`, [id, props])
.then(res => { return res })
.catch(err => {
console.warn(`error dispatching ${props} to document ${id} in ${collection}`, err)
return null
}
)
}
},
// doc can be entire document or the id
getDocProp (collection, id, prop) {
// console.log('get doc property', collection, id, prop)
if (arguments.length === 1) {
if (isPlainObj(collection)) {
if (collection.path) [collection, id, prop] = collection.path.split('.')
else ({ collection, id, prop } = collection)
} else [collection, id, prop] = collection.split('.')
}
if (id && collection) {
// console.log('get doc property', collection, id, prop)
let doc = this.$store.getters[`${collection}/get`](id)
return prop ? doc[prop] : doc
}
return null
},
toggleDocProp () {
// console.log('toggling prop', arguments)
let value = !this.getDocProp(...arguments)
if (typeof value === 'boolean') this.setDocProp(...arguments, value)
else console.warn('property to toggle was not a boolean', arguments, value)
}
}
}
function bind (ctx, collection) {
ctx.setDocProp = ctx.setDocProp.bind(ctx, collection)
ctx.toggleDocProp = ctx.toggleDocProp.bind(ctx, collection)
ctx.getDocProp = ctx.getDocProp.bind(ctx, collection)
}
function isPlainObj (obj) {
return Object.prototype.toString.call(obj).indexOf('Object') !== -1
}
export default prop
export { bind as bindCollection }
Current Modeling API docs are found here: https://vuex.feathersjs.com/model-classes.html
I mean I have generic service component which wraps a generic collection component so to use a specific instance I need to bind the actual service/collection name on the db calls.
This is exactly how I use the modeling API. I write generic components built around its interface.
But since my sevrice component requires a service name property I can't build this "api" beforehand I need to create/bind to a generic api at run time.
Makes sense, and should work well. You can either pass in a Model like I mentioned a couple comments ago, or you can provide the name of the model as found in the models.api object. When I'm doing dynamic stuff, I usually prefer to pass the Model in as a prop. I'll then create a parent component whose responsibility is specifically to provide the appropriate model class to its child component.
This is a pretty powerful feature, too: https://vuex.feathersjs.com/model-classes.html#model-events
I wrote a whole nest of components service => collection => document => form
the service and collection component have slots for adding service/collection related stuff around the collection. I also have a totally separate data system for passing json packets to/from a backend socket. I wrote get/set functions for the store that manaage a reactive any depth nested object (with a root namespace) and so I have a similar mixin for getting and setting those values. So what you say. "One of my plans for Feathers-Vuex is to decouple it from Feathers so that other sources can be used by building plugins that mimic the current Model API." that is of great interest to me.
Sounds like we've been doing some crossover work. ;) We're getting there, bit by bit.
This is going way beyond my issue for which I have a solution for now without a major refactor. Thx
But here is my service component. You see I have service prop which is just the name which I use to bind the mixin in the created() function. If I could directly bind to a 'data model' api for fv data or my nested object data then I could dump mixin which seems way better.
<template>
<q-page :class="service">
<collection
:key="name"
:collection="service"
:docs="docs"
:schema="schema"
:optionSets="sets"
:readOnly="readonly"
:indexKey="indexKey || 'name' "
@changed="changed"
>
<template v-slot:col:before="{ col }">
<!-- Test {{ col }} -->
<component v-if="serviceSlot('before')"
:key="`${service}-before`"
:is="serviceSlot('before')"
:docs="col.docs || docs"
:schema="col.schema || schema"
:collection="col.name || service"
@filtered="filtered"
keep-alive>
</component>
</template>
<template v-slot:col:buttons="{ col }">
<!-- Test {{ col }} -->
<component v-if="serviceSlot('buttons')"
:key="`${service}-buttons`"
:is="serviceSlot('buttons')"
:docs="col.docs || docs"
:schema="col.schema || schema"
:collection="col.name || service"
keep-alive>
</component>
</template>
<template v-slot:doc:prepend="{ doc }">
<!-- Test {{ service }} {{doc.name}} -->
<component v-if="serviceSlot('prepend')"
:key="`${doc._id}-prepend`"
:is="serviceSlot('prepend')"
:service="service"
:doc="doc"
@toggle="toggleDocProp"
@send="send"
keep-alive>
</component>
</template>
<template v-slot:doc:append="{ doc }">
<!-- {{ service }} {{doc.name}} -->
<component v-if="serviceSlot('append')"
:key="`${doc._id}-append`"
:is="serviceSlot('append')"
:service="service"
:doc="doc"
@toggle="toggleDocProp"
@send="send"
keep-alive>
</component>
<!-- <slot name='doc:append' :doc="doc"/> -->
</template>
</collection>
</q-page>
</template>
<script>
// const SERVICE_DIR = 'src/pages/services' // eslint-disable-line no-unused-vars
import Collection from 'src/components/Collection.vue'
// import booleans from 'src/mixins/booleans'
import state, { bindState } from 'src/mixins/state'
import docProp, { bindCollection } from 'src/mixins/doc-prop'
import services from 'src/services' // adds per service slot components
// import to from 'await-to-js'
// import network from 'src/pages/services/network.vue'
export default {
data () {
return {
name: this.$route.params.name,
readonly: false,
sets: {},
schema: {},
docs: null // the collection of a service, this will be obtained by store/db means
}
},
mixins: [state, docProp],
props: ['service', 'indexKey', 'readOnly', 'optionSets'], // must pass service name as prop in either route call or calling template
components: {
Collection: Collection
},
computed: {
},
methods: {
changed (change) {
// console.log('change emitted all the way back to service', change)
},
filtered (docs) {
// console.log('filtered docs', docs)
this.docs = docs
},
serviceSlot (slot) {
// console.log(services, slot, services[slot])
return services[slot] ? services[slot][this.service] : null
},
send (cmd, doc, modifier) {
// if (typeof cmd !== 'string')({ cmd, doc, modifier } = cmd)
// console.log('controller send request at service component', cmd, doc, modifier)
// this.$socket.send({ cmd: cmd })
// this.toggleDocProp(doc, cmd) // mock of command to master
}
},
beforeRouteUpdate (to, from, next) {
this.name = to.params.name
next()
},
beforeCreate () {
// this.addServiceMethods()
},
created () {
// this.services = services
bindState(this, this.service, 'state')
bindCollection(this, this.service)
},
async mounted () {
this.readonly = this.readOnly
}
}
</script>
<style lang="stylus">
</style>
The Modeling API should totally work for your situation.
Well finally, all these components are based on quasar (thus the q- tags). The cool one is the form component which is a dynamically rendered template based on a schema object which one gets from backend via a special 'schema' get id and a beforehook in the backend. You change/add a schema prop in the schema file in the backend and boom the document form has a new field and maybe behaviors. No change in the frontend code at all! I guess I need to get all this published at github with some explanation/documentation. But I am two weeks from an install of a 48 switch/circuit lighting application for which I wrote all this and need to get that done first. Anyway thanks for all your efforts. FV has been a critical piece of making it all work. So thx one more time.

Ah very cool. Thanks for sharing. Looks amazing!