8000 feat: new & simpler meteor data syntax + meteor components · meteor-vue/vue-meteor-tracker@b0b4eef · GitHub
[go: up one dir, main page]

Skip to content

Commit b0b4eef

Browse files
author
Guillaume Chau
committed
feat: new & simpler meteor data syntax + meteor components
1 parent ea4a7df commit b0b4eef

File tree

5 files changed

+384
-262
lines changed

5 files changed

+384
-262
lines changed

index.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/components/MeteorData.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// @vue/component
2+
export default {
3+
name: 'MeteorData',
4+
5+
props: {
6+
query: {
7+
type: Function,
8+
required: true,
9+
},
10+
11+
tag: {
12+
type: String,
13+
default: 'div',
14+
},
15+
},
16+
17+
watch: {
18+
query: {
19+
handler (value) {
20+
if (this.$_unwatch) this.$_unwatch()
21+
this.$_unwatch = this.$addMeteorData('meteorData', value)
22+
},
23+
immediate: true,
24+
},
25+
},
26+
27+
render (h) {
28+
let result = this.$scopedSlots.default({
29+
data: this.meteorData,
30+
})
31+
if (Array.isArray(result)) {
32+
result = result.concat(this.$slots.default)
33+
} else {
34+
result = [result].concat(this.$slots.default)
35+
}
36+
return h(this.tag, result)
37+
},
38+
}

src/components/MeteorSub.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// @vue/component
2+
export default {
3+
name: 'MeteorSub',
4+
5+
props: {
6+
name: {
7+
type: String,
8+
required: true,
9+
},
10+
11+
parameters: {
12+
type: [Array, Function],
13+
default: undefined,
14+
},
15+
16+
tag: {
17+
type: String,
18+
default: 'div',
19+
},
20+
},
21+
22+
watch: {
23+
name: 'updateSub',
24+
parameters (value, oldValue) {
25+
if (typeof value !== typeof oldValue) this.updateSub()
26+
},
27+
},
28+
29+
created () {
30+
this.updateSub()
31+
},
32+
33+
methods: {
34+
updateSub () {
35+
if (this.$_unsub) this.$_unsub()
36+
const parameters = typeof this.parameters === 'function' ? this.parameters : () => this.parameters || []
37+
this.$_unsub = this.$addReactiveSub(this.name, parameters)
38+
},
39+
},
40+
41+
render (h) {
42+
let result = this.$scopedSlots.default({
43+
loading: !this.$subReady[this.name],
44+
})
45+
if (Array.isArray(result)) {
46+
result = result.concat(this.$slots.default)
47+
} else {
48+
result = [result].concat(this.$slots.default)
49+
}
50+
return h(this.tag, result)
51+
},
52+
}

src/index.js

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
import omit from 'lodash.omit'
2+
// Components
3+
import CMeteorData from './components/MeteorData'
4+
import CMeteorSub from './components/MeteorSub'
5+
6+
function defaultSubscription (...args) {
7+
return Meteor.subscribe(...args)
8+
}
9+
10+
function hasProperty (holder, key) {
11+
return typeof holder !== 'undefined' && holder.hasOwnProperty(key)
12+
}
13+
14+
const noop = () => {}
15+
16+
let set
17+
18+
export default {
19+
install (Vue, options) {
20+
const isServer = Vue.prototype.$isServer
21+
const vueVersion = Vue.version.substr(0, Vue.version.indexOf('.'))
22+
23+
Vue.config.meteor = {
24+
subscribe: defaultSubscription,
25+
freeze: false,
26+
}
27+
28+
set = Vue.set
29+
30+
for (const k in options) {
31+
Vue.config.meteor[k] = options[k]
32+
}
33+
34+
const merge = Vue.config.optionMergeStrategies.methods
35+
Vue.config.optionMergeStrategies.meteor = function (toVal, fromVal, vm) {
36+
if (!toVal) return fromVal
37+
if (!fromVal) return toVal
38+
39+
const toData = Object.assign({}, omit(toVal, [
40+
'subscribe',
41+
'data',
42+
]), toVal.data)
43+
const fromData = Object.assign({}, omit(fromVal, [
44+
'subscribe',
45+
'data',
46+
]), fromVal.data)
47+
48+
return Object.assign({
49+
subscribe: merge(toVal.subscribe, fromVal.subscribe),
50+
}, merge(toData, fromData))
51+
}
52+
53+
function prepare () {
54+
this._trackerHandles = []
55+
this._subsAutorun = {}
56+
this._subs = {}
57+
58+
Object.defineProperty(this, '$subReady', {
59+
get: () => this.$data.$meteor.subs,
60+
enumerable: true,
61+
configurable: true,
62+
})
63+
}
64+
65+
function launch () {
66+
this._meteorActive = true
67+
68+
let meteor = this.$options.meteor
69+
70+
if (meteor) {
71+
let ssr = true
72+
if (typeof meteor.$ssr !== 'undefined') {
73+
ssr = meteor.$ssr
74+
}
75+
76+
if (!isServer || ssr) {
77+
// Subscriptions
78+
if (meteor.subscribe || meteor.$subscribe) {
79+
const subscribeOptions = Object.assign({}, meteor.subscribe, meteor.$subscribe)
80+
for (let key in subscribeOptions) {
81+
this.$addReactiveSub(key, subscribeOptions[key])
82+
}
83+
}
84+
85+
const data = Object.assign({}, omit(meteor, [
86+
'subscribe',
87+
'data',
88+
]), meteor.data)
89+
90+
// Reactive data
91+
if (data) {
92+
for (let key in data) {
93+
if (key.charAt(0) !== '$') {
94+
const options = data[key]
95+
let func
96+
if (typeof options === 'function') {
97+
func = options.bind(this)
98+
} else {
99+
throw Error(`Meteor data '${key}': You must provide a function which returns the result.`)
100+
}
101+
102+
this.$addMeteorData(key, func)
103+
}
104+
}
105+
}
106+
}
107+
}
108+
}
109+
110+
Vue.mixin({
111+
data () {
112+
return {
113+
$meteor: {
114+
data: {},
115+
subs: {},
116+
},
117+
}
118+
},
119+
120+
...vueVersion === '1' ? {
121+
init: prepare,
122+
} : {},
123+
124+
...vueVersion === '2' ? {
125+
beforeCreate: prepare,
126+
} : {},
127+
128+
created () {
129+
if (this.$options.meteor && !this.$options.meteor.$lazy) {
130+
launch.call(this)
131+
}
132+
},
133+
134+
destroyed: function () {
135+
this.$stopMeteor()
136+
},
137+
138+
methods: {
139+
$subscribe (...args) {
140+
if (args.length > 0) {
141+
const key = args[0]
142+
const oldSub = this._subs[key]
143+
let handle = Vue.config.meteor.subscribe.apply(this, args)
144+
this._trackerHandles.push(handle)
145+
this._subs[key] = handle
146+
147+
// Readiness
148+
if (typeof handle.ready === 'function') {
149+
set(this.$data.$meteor.subs, key, false)
150+
if (this._subsAutorun[key]) {
151+
this._subsAutorun[key].stop()
152+
}
153+
const autorun = this.$autorun(() => {
154+
const ready = handle.ready()
155+
set(this.$data.$meteor.subs, key, ready)
156+
// Wait for the new subscription to be ready before stoping the old one
157+
if (ready && oldSub) {
158+
this.$stopHandle(oldSub)
159+
}
160+
})
161+
this._subsAutorun[key] = autorun
162+
}
163+
164+
return handle
165+
// }
166+
} else {
167+
throw new Error('You must provide the publication name to $subscribe.')
168+
}
169+
},
170+
171+
$autorun (reactiveFunction) {
172+
let handle = Tracker.autorun(reactiveFunction)
173+
this._trackerHandles.push(handle)
174+
return handle
175+
},
176+
177+
$stopHandle (handle) {
178+
handle.stop()
179+
let index = this._trackerHandles.indexOf(handle)
180+
if (index !== -1) {
181+
this._trackerHandles.splice(index, 1)
182+
}
183+
},
184+
185+
$startMeteor () {
186+
if (!this._meteorActive) {
187+
launch.call(this)
188+
}
189+
},
190+
191+
$stopMeteor () {
192+
// Stop all reactivity when view is destroyed.
193+
this._trackerHandles.forEach((tracker) => {
194+
try {
195+
tracker.stop()
196+
} catch (e) {
197+
console.error(e, tracker)
198+
}
199+
})
200+
this._trackerHandles = null
201+
this._meteorActive = false
202+
},
203+
204+
$addReactiveSub (key, options) {
205+
let handle, unwatch
206+
let subscribe = params => {
207+
handle = this.$subscribe(key, ...params)
208+
}
209+
210+
if (typeof options === 'function') {
211+
if (isServer) {
212+
subscribe(options.bind(this)())
213+
} else {
214+
unwatch = this.$watch(options, params => {
215+
subscribe(params)
216+
}, {
217+
immediate: true,
218+
})
219+
}
220+
} else {
221+
subscribe(options)
222+
}
223+
224+
return () => {
225+
if (unwatch) unwatch()
226+
if (handle) this.$stopHandle(handle)
227+
}
F438
228+
},
229+
230+
$addMeteorData (key, func) {
231+
const hasDataField = hasProperty(this.$data, key)
232+
if (!hasDataField && !hasProperty(this, key) && !hasProperty(this.$props, key)) {
233+
Object.defineProperty(this, key, {
234+
get: () => this.$data.$meteor.data[key],
235+
enumerable: true,
236+
configurable: true,
237+
})
238+
}
239+
240+
const setData = value => {
241+
set(hasDataField ? this.$data : this.$data.$meteor.data, key, value)
242+
}
243+
244+
setData(null)
245+
246+
// Function run
247+
const run = (params) => {
248+
let result = func(params)
249+
if (result && typeof result.fetch === 'function') {
250+
result = result.fetch()
251+
}
252+
if (Vue.config.meteor.freeze) {
253+
result = Object.freeze(result)
254+
}
255+
setData(result)
256+
}
257+
258+
// Meteor autorun
259+
let computation
260+
const unautorun = () => {
261+
if (computation) this.$stopHandle(computation)
262+
}
263+
const autorun = () => {
264+
unautorun()
265+
computation = this.$autorun(() => {
266+
run()
267+
})
268+
}
269+
270+
// Vue autorun
271+
const unwatch = this.$watch(autorun, noop, {
272+
immediate: true,
273+
})
274+
275+
return () => {
276+
unwatch()
277+
unautorun()
278+
}
279+
},
280+
},
281+
})
282+
283+
if (vueVersion === '2') {
284+
// Components
285+
Vue.component('MeteorData', CMeteorData)
286+
Vue.component('meteor-data', CMeteorData)
287+
Vue.component('MeteorSub', CMeteorSub)
288+
Vue.component('meteor-sub', CMeteorSub)
289+
}
290+
},
291+
}
292+
293+
export const MeteorData = CMeteorData
294+
export const MeteorSub = CMeteorSub

0 commit comments

Comments
 (0)
0