diff --git a/gun.js b/gun.js index c8db8e06..c8e44415 100644 --- a/gun.js +++ b/gun.js @@ -800,7 +800,7 @@ }()); Gun.chain.on = function(cb, opt){ // on subscribes to any changes on the souls. - var gun = this, u; + var gun = this, u, oldoff = this.off; opt = Gun.obj.is(opt)? opt : {change: opt}; cb = cb || function(){}; function map(at){ @@ -815,6 +815,7 @@ }; opt.on = gun._.at('soul').map(map); if(gun === gun.back){ Gun.log('You have no context to `.on`!') } + gun.off = oldoff ? function() { oldoff(); opt.on.off(); } : opt.on.off // Chain offs return gun; } @@ -850,7 +851,7 @@ return function(path, cb, opt){ opt = opt || {}; cb = cb || (function(){ var cb = function(){}; cb.no = true; return cb }()); cb.hash = {}; - var gun = this, chain = gun.chain(), f, c, u; + var gun = this, chain = gun.chain(), ons = [], f, c, u; if(!Gun.list.is(path)){ if(!Gun.text.is(path)){ if(!Gun.num.is(path)){ // if not a list, text, or number return cb.call(chain, {err: Gun.log("Invalid path '" + path + "'!")}), chain; // then complain } else { return this.path(path + '', cb, opt) } } else { return this.path(path.split('.'), cb, opt) } } // else coerce upward to a list. @@ -858,7 +859,7 @@ cb.call(chain, opt.put? null : {err: Gun.log('You have no context to `.path`', path, '!')}, opt.put? gun.__.graph[(path||[])[0]] : u); return chain; } - gun._.at('path:' + path[0]).event(function(at){ + ons.push(gun._.at('path:' + path[0]).event(function(at){ if(opt.done){ this.off(); return } // TODO: BUG - THIS IS A FIX FOR A BUG! TEST #"context no double emit", COMMENT THIS LINE OUT AND SEE IT FAIL! var ctx = {soul: at.soul, field: at.field, by: gun.__.by(at.soul)}, field = path[0]; var on = Gun.obj.as(cb.hash, at.hash, {off: function(){}}); @@ -879,8 +880,8 @@ } if(1 === path.length){ cb.call(ctx.by.chain || chain, null, at.value, ctx.field) } chain._.at('soul').emit(at).chain(opt.chain); - }); - gun._.at('null').only(function(at){ + })); + ons.push(gun._.at('null').only(function(at){ if(!at.field){ return } if(at.not){ gun.put({}, null, {init: true}); @@ -889,8 +890,8 @@ (at = Gun.on.at.copy(at)).field = path[0]; at.not = true; chain._.at('null').emit(at).chain(opt.chain); - }); - gun._.at('end').event(function(at){ + })); + ons.push(gun._.at('end').event(function(at){ this.off(); if(at.at && at.at.field === path[0]){ return } // TODO: BUG! THIS FIXES SO MANY PROBLEMS BUT DOES IT CATCH VARYING SOULS EDGE CASE? var ctx = {by: gun.__.by(at.soul)}; @@ -899,10 +900,15 @@ at.not = true; cb.call(ctx.by.chain || chain, null); chain._.at('null').emit(at).chain(opt.chain); - }); + })); if(path.length > 1){ (c = chain.path(path.slice(1), cb, opt)).back = gun; } + (c || chain).off = function() { + ons.forEach(function(on) { + on.off(); + }) + }; return c || chain; } }()); @@ -923,7 +929,7 @@ return; } } - cb.hash[this.soul + field] = cb.hash[this.soul + field] || this.gun.path(field, path, {chain: chain, via: 'map'}); // TODO: path should reuse itself! We shouldn't have to do it ourselves. + cb.hash[this.soul + field] = cb.hash[this.soul + field] || (pathon = this.gun.path(field, path, {chain: chain, via: 'map'})); // TODO: path should reuse itself! We shouldn't have to do it ourselves. // TODO: // 1. Ability to turn off an event. // automatically happens within path since reusing is manual? // 2. Ability to pass chain context to fire on. // DONE @@ -934,7 +940,11 @@ var ref = gun.__.by(at.soul).chain || gun; Gun.is.node(at.change, each, {gun: ref, soul: at.soul}); } - gun.on(map, {raw: true, change: true}); // TODO: ALLOW USER TO DO map change false! + on = gun.on(map, {raw: true, change: true}); // TODO: ALLOW USER TO DO map change false! + chain.off = function() { + if (pathon) pathon.off(); + on.off(); + } if(gun === gun.back){ Gun.log('You have no context to `.map`!') } return chain; } diff --git a/test/common.js b/test/common.js index ef9ae2e7..1ac1cd00 100644 --- a/test/common.js +++ b/test/common.js @@ -4190,4 +4190,51 @@ describe('Gun', function(){ }, 100); }); }); -}); \ No newline at end of file +}); +describe('On', function(){ + it('emits to former subscribers', function() { + var recv = null; + Gun.on('on-test-1').event(function(val) { + recv = val; + }); + Gun.on('on-test-1').emit('foo'); + expect(recv).to.be('foo'); + }); + it('does not emit to future subscribers', function() { + var recv = null; + Gun.on('on-test-1').emit('foo'); + Gun.on('on-test-1').event(function(val) { + recv = val; + }); + expect(recv).to.be(null); + }); + it('on subscriptions can unsubscribe', function() { + var gun = Gun(); + var recv; + gun.get('on-test-3').put({v: 'foo'}); + var sub = gun.get('on-test-3').on(function(o) { + recv = o.v; + }); + expect(recv).to.be('foo'); + gun.get('on-test-3').put({v: 'bar'}); + expect(recv).to.be('bar'); + sub.off(); + gun.get('on-test-3').put({v: 'off'}); + expect(recv).to.be('bar'); + }); + it('map subscriptions can unsubscribe', function() { + var gun = Gun(); + var recv; + gun.get('on-test-4').put({v: 'foo'}); + var sub = gun.get('on-test-4').map(function(v, k) { + if (v == 'off') throw new Error('unexpected'); + recv = v; + }); + expect(recv).to.be('foo'); + gun.get('on-test-4').put({v: 'bar'}); + expect(recv).to.be('bar'); + sub.off(); + gun.get('on-test-4').put({v: 'off'}); + expect(recv).to.be('bar'); + }); +});