diff --git a/README.md b/README.md index bc8752a..97dac29 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,36 @@ var vm = new Vue({ }) ``` +#### `$createObservableMethod(methodName)` + +> This feature requires RxJS. + +Convert function calls to observable sequence which emits the call arguments. + +This is a prototype method added to instances. Use it to create a shared hot observable from a function name. The function will assigned as a vm method. + +```html + +``` +``` js +var vm = new Vue({ + subscriptions () { + return { + // requires `share` operator + formData: this.$createObservableMethod('submitHandler') + } + } +}) +``` +Or, use the `observableMethods` convenience option: +``` js +new Vue({ + observableMethods: { submitHandler:'submitHandler$' } +}) +``` +[example](https://github.com/vuejs/vue-rx/blob/master/example/counter-function.html) + + ### Caveats You cannot use the `watch` option to watch subscriptions, because it is processed before the subscriptions are set up. But you can use `$watch` in the `created` hook instead. diff --git a/example/counter-function.html b/example/counter-function.html new file mode 100644 index 0000000..6b5a06e --- /dev/null +++ b/example/counter-function.html @@ -0,0 +1,47 @@ + + + + + + +
+
{{ count }}
+ + + + + + +
{{ $data }}
+ +
+ + diff --git a/package.json b/package.json index fe71d31..a9e5132 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-rx", - "version": "3.2.0", + "version": "3.3.0", "description": "RxJS bindings for Vue", "main": "dist/vue-rx.js", "files": [ diff --git a/src/index.js b/src/index.js index 461a93d..7919dcf 100644 --- a/src/index.js +++ b/src/index.js @@ -7,6 +7,7 @@ import watchAsObservable from './methods/watchAsObservable' import fromDOMEvent from './methods/fromDOMEvent' import subscribeTo from './methods/subscribeTo' import eventToObservable from './methods/eventToObservable' +import createObservableMethod from './methods/createObservableMethod' export default function VueRx (Vue, Rx) { install(Vue, Rx) @@ -16,6 +17,7 @@ export default function VueRx (Vue, Rx) { Vue.prototype.$fromDOMEvent = fromDOMEvent Vue.prototype.$subscribeTo = subscribeTo Vue.prototype.$eventToObservable = eventToObservable + Vue.prototype.$createObservableMethod = createObservableMethod } // auto install diff --git a/src/methods/createObservableMethod.js b/src/methods/createObservableMethod.js new file mode 100644 index 0000000..c4ee27b --- /dev/null +++ b/src/methods/createObservableMethod.js @@ -0,0 +1,56 @@ +import { Rx, hasRx, warn } from '../util' + +/** + * @name Vue.prototype.$createObservableMethod + * @description Creates an observable from a given function name. + * @param {String} methodName Function name + * @param {Boolean} [passContext] Append the call context at the end of emit data? + * @return {Observable} Hot stream + */ +export default function createObservableMethod (methodName, passContext) { + if (!hasRx()) { + return + } + const vm = this + + if (!Rx.Observable.prototype.share) { + warn( + `No 'share' operator. ` + + `$createObservableMethod returns a shared hot observable. ` + + `Try import 'rxjs/add/operator/share' for creating ${methodName}`, + vm + ) + return + } + + if (vm[methodName] !== undefined) { + warn( + 'Potential bug: ' + + `Method ${methodName} already defined on vm and has been overwritten by $createObservableMethod.` + + String(vm[methodName]), + vm + ) + } + + const creator = function (observer) { + vm[methodName] = function () { + const args = Array.from(arguments) + if (passContext) { + args.push(this) + observer.next(args) + } else { + if (args.length <= 1) { + observer.next(args[0]) + } else { + observer.next(args) + } + } + } + return function () { + delete vm[methodName] + } + } + + // Must be a hot stream otherwise function context may overwrite over and over again + return Rx.Observable.create(creator).share() +} diff --git a/src/mixin.js b/src/mixin.js index 3363976..e8282c4 100644 --- a/src/mixin.js +++ b/src/mixin.js @@ -14,6 +14,19 @@ export default { } } + const observableMethods = vm.$options.observableMethods + if (observableMethods) { + if (Array.isArray(observableMethods)) { + observableMethods.forEach(methodName => { + vm[ methodName + '$' ] = vm.$createObservableMethod(methodName) + }) + } else { + Object.keys(observableMethods).forEach(methodName => { + vm[observableMethods[methodName]] = vm.$createObservableMethod(methodName) + }) + } + } + let obs = vm.$options.subscriptions if (typeof obs === 'function') { obs = obs.call(vm) diff --git a/test/test.js b/test/test.js index 8ea674c..7a3a7e2 100644 --- a/test/test.js +++ b/test/test.js @@ -10,6 +10,7 @@ const Observable = require('rxjs/Observable').Observable const Subject = require('rxjs/Subject').Subject const Subscription = require('rxjs/Subscription').Subscription require('rxjs/add/observable/fromEvent') +require('rxjs/add/operator/share') // user require('rxjs/add/operator/map') @@ -316,3 +317,69 @@ test('$eventToObservable() with lifecycle hooks', done => { vm.$destroy() }) }) + +test('$createObservableMethod() with no context', done => { + const vm = new Vue({ + created () { + this.$createObservableMethod('add') + .subscribe(function (param) { + expect(param).toEqual('hola') + done(param) + }) + } + }) + nextTick(() => { + vm.add('hola') + }) +}) + +test('$createObservableMethod() with muli params & context', done => { + const vm = new Vue({ + created () { + this.$createObservableMethod('add', true) + .subscribe(function (param) { + expect(param[0]).toEqual('hola') + expect(param[1]).toEqual('mundo') + expect(param[2]).toEqual(vm) + done(param) + }) + } + }) + nextTick(() => { + vm.add('hola', 'mundo') + }) +}) + +test('observableMethods mixin', done => { + const vm = new Vue({ + observableMethods: ['add'], + created () { + this.add$ + .subscribe(function (param) { + expect(param[0]).toEqual('Qué') + expect(param[1]).toEqual('tal') + done(param) + }) + } + }) + nextTick(() => { + vm.add('Qué', 'tal') + }) +}) + +test('observableMethods mixin', done => { + const vm = new Vue({ + observableMethods: { 'add': 'plus$' }, + created () { + this.plus$ + .subscribe(function (param) { + expect(param[0]).toEqual('Qué') + expect(param[1]).toEqual('tal') + done(param) + }) + } + }) + nextTick(() => { + vm.add('Qué', 'tal') + }) +})