Skip to content

Instantly share code, notes, and snippets.

@jdkanani
Created August 28, 2017 19:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdkanani/a81d09bad12f1bab028ab1e651c82803 to your computer and use it in GitHub Desktop.
Save jdkanani/a81d09bad12f1bab028ab1e651c82803 to your computer and use it in GitHub Desktop.
vuexfire + vuefire
import Vue from 'vue';
import { VueFireMixin, firebaseMutations, bind, unbind } from '~/plugins/vuefire';
// vuefire init
export default function ({ store }) {
// use object-based merge strategy
// TODO This makes impossible to merge functions
const mergeStrats = Vue.config.optionMergeStrategies;
mergeStrats.firebase = mergeStrats.methods;
// For normal vuefire: mutate vm's key directly while committing
function commit (type, ...args) {
firebaseMutations[type](this, ...args);
}
// extend instance methods
// bind as object
Vue.prototype.$bindAsObject = function (key, source, options = {}) {
options.asObject = true;
bind({ commit: commit.bind(this), state: this }, key, source, options);
};
// bind as array
Vue.prototype.$bindAsArray = function (key, source, options = {}) {
bind({ commit: commit.bind(this), state: this }, key, source, options);
};
// unbind
Vue.prototype.$unbind = function (key) {
unbind({ commit: commit.bind(this), state: this }, key);
};
// mixin
Vue.mixin(VueFireMixin);
}
/* eslint-disable */
import Vue from 'vue';
// mutations
const VUEXFIRE_VALUE = 'fire:onValue';
const VUEXFIRE_ARRAY_ADD = 'fire:onChildAdded';
const VUEXFIRE_ARRAY_CHANGE = 'fire:onChildChanged';
const VUEXFIRE_ARRAY_MOVE = 'fire:onChildMoved';
const VUEXFIRE_ARRAY_REMOVE = 'fire:onChildRemoved';
const commitOptions = { root: true };
// check if object
const isObject = val => Object.prototype.toString.call(val) === '[object Object]';
const normalizeMap = map => (Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] })));
// Get key and ref
const getKey = snapshot => (typeof snapshot.key === 'function' ? snapshot.key() : snapshot.key);
const getRef = (refOrQuery) => {
let result;
if (typeof refOrQuery.ref === 'function') {
result = refOrQuery.ref();
} else if (typeof refOrQuery.ref === 'object') {
result = refOrQuery.ref;
}
return result;
};
// create record
const createRecord = (snapshot, Model) => {
const value = snapshot.val();
let result;
if (isObject(value)) {
result = Model ? new Model(value) : value;
} else {
result = { '.value': value };
}
result['.key'] = getKey(snapshot);
return result;
};
// get index for key
const indexForKey = (array, key) => {
let i;
for (i = 0; i < array.length; i += 1) {
if (array[i]['.key'] === key) {
return i;
}
}
return -1;
};
export const bind = (vm, key, source, options) => {
if (!vm.state.$firebaseRefs) {
vm.state.$firebaseRefs = Object.create(null);
vm.state.$firebaseSources = Object.create(null);
vm.state.$firebaseListeners = Object.create(null);
}
const asObject = !!options.asObject;
const ref = getRef(source);
vm.state.$firebaseRefs[key] = ref;
vm.state.$firebaseSources[key] = source;
// bind based on initial value type
if (asObject) {
bindAsObject(vm, key, source, options);
} else {
bindAsArray(vm, key, source, options);
}
options.readyCallback && source.once('value', options.readyCallback.bind(vm));
};
export const unbind = (vm, key) => {
const source = vm.state.$firebaseSources && vm.state.$firebaseSources[key];
if (!source) {
throw new Error(`VueFire: unbind failed: ${key} is not bound to a Firebase reference.`);
}
const listeners = vm.state.$firebaseListeners[key];
Object.keys(listeners).forEach((event) => {
source.off(event, listeners[event]);
});
// set value to vuex or simple component
vm.commit(VUEXFIRE_VALUE, { key, value: null }, commitOptions);
vm.state.$firebaseRefs[key] = null;
vm.state.$firebaseSources[key] = null;
vm.state.$firebaseListeners[key] = null;
};
const bindAsObject = (vm, key, source, options) => {
// set value to vuex or simple component
if (!vm.state[key]) {
vm.commit(VUEXFIRE_VALUE, { key, value: {} }, commitOptions);
}
const cancelCallback = options.cancelCallback;
let func = options.once ? source.once : source.on;
func = func.bind(source); // need to bind with source object
const cb = func('value', (snapshot) => {
const value = createRecord(snapshot, options.model);
// set value
vm.commit(VUEXFIRE_VALUE, { key, value }, commitOptions);
// on change callback
options.onChange && options.onChange(value);
}, cancelCallback);
vm.state.$firebaseListeners[key] = { value: cb };
};
const bindAsArray = (vm, key, source, options) => {
if (!vm.state[key]) {
// set value to vuex or simple component
vm.commit(VUEXFIRE_VALUE, { key, value: [] }, commitOptions);
}
const array = vm.state[key];
const cancelCallback = options.cancelCallback;
const onAdd = source.on('child_added', (snapshot, prevKey) => {
const index = prevKey ? indexForKey(array, prevKey) + 1 : array.length;
const record = createRecord(snapshot, options.model);
vm.commit(VUEXFIRE_ARRAY_ADD, { key, index, record, array }, commitOptions);
options.onChildAdded && options.onChildAdded(record);
}, cancelCallback);
const onRemove = source.on('child_removed', (snapshot) => {
const index = indexForKey(array, getKey(snapshot));
const record = array[index];
vm.commit(VUEXFIRE_ARRAY_REMOVE, { key, index, record, array }, commitOptions);
options.onChildRemoved && options.onChildRemoved(record);
}, cancelCallback);
const onChange = source.on('child_changed', (snapshot) => {
const index = indexForKey(array, getKey(snapshot));
const record = createRecord(snapshot, options.model);
vm.commit(VUEXFIRE_ARRAY_CHANGE, { key, index, record, array }, commitOptions);
options.onChildChanged && options.onChildChanged(record);
}, cancelCallback);
const onMove = source.on('child_moved', (snapshot, prevKey) => {
const index = indexForKey(array, getKey(snapshot));
const record = createRecord(snapshot, options.model);
let newIndex = prevKey ? indexForKey(array, prevKey) + 1 : 0;
// TODO refactor
newIndex += index < newIndex ? -1 : 0;
vm.commit(VUEXFIRE_ARRAY_MOVE, { key, index, record, newIndex, array }, commitOptions);
}, cancelCallback);
vm.state.$firebaseListeners[key] = {
child_added: onAdd,
child_removed: onRemove,
child_changed: onChange,
child_moved: onMove,
};
};
// mutations
export const firebaseMutations = {
[VUEXFIRE_VALUE] (state, { key, value }) {
state[key] = value;
},
[VUEXFIRE_ARRAY_ADD] (state, { key, index, record, array }) {
array = array || state[key];
array.splice(index, 0, record);
},
[VUEXFIRE_ARRAY_CHANGE] (state, { key, index, record, array }) {
array = array || state[key];
array.splice(index, 1, record);
},
[VUEXFIRE_ARRAY_MOVE] (state, { key, index, record, newIndex, array }) {
array = array || state[key];
array.splice(newIndex, 0, array.splice(index, 1)[0]);
},
[VUEXFIRE_ARRAY_REMOVE] (state, { key, index, array }) {
array = array || state[key];
array.splice(index, 1);
},
};
// actions
export const firebaseActions = {
bindFirebaseRef (context, { key, source, options = {} }) {
const { state, commit } = context;
bind({ state, commit }, key, source, options);
},
unbindFirebaseRef (context, { key }) {
const { state, commit } = context;
unbind({ state, commit }, key);
},
};
// vue fire mixin
export const VueFireMixin = {
beforeDestroy () {
if (!this.$firebaseRefs) return;
Object.keys(this.$firebaseRefs).forEach((key) => {
if (this.$firebaseRefs[key]) {
this.$unbind(key);
}
});
this.$firebaseRefs = null;
this.$firebaseSources = null;
this.$firebaseListeners = null;
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment