kefirjs / kefir

A Reactive Programming library for JavaScript
https://kefirjs.github.io/kefir/
MIT License
1.87k stars 97 forks source link

Kefir.model implementation #109

Closed a-s-o closed 9 years ago

a-s-o commented 9 years ago

Hello, I would like to know whether Kefir.model is still going to be implemented. I saw that there was some discussion in #43. I am presently using the following solution:

Please see gist at: https://gist.github.com/a-s-o/0911caf8f29d2195acef

/////////////
// Factory //
/////////////

Kefir.model = function (store, tx) {
   if (typeof tx !== 'function') tx = x => x;

   const getset = function kefir$model (update) {
      if (arguments.length > 0) {
         let value = tx(update, (getset._currentEvent || {}).value);

         if (getset._alive) {
            // Active property (kefir will update current value)
            getset._emitValue(value);
         } else {
            // In-active property (manually set current value)
            getset._currentEvent = { type: VALUE, value: value };
         }

         return value;
      }
      return (getset._currentEvent || {}).value;
   };

   // Copy property methods to model
   let proto = Kefir.Property.prototype;
   for (var x in proto) getset[x] = proto[x];
   Kefir.Property.call(getset);

   getset._currentEvent = { type: VALUE, value: store };

   return getset;
};

///////////////
// Converter //
///////////////

Kefir.Observable.prototype.toModel = function(store, tx) {
   if (typeof tx !== 'function') tx = x => x;

   // Sources
   let model = Kefir.model(store, tx);
   let obs = this.map(update => tx(update, store));

   let getset = function (update) {
      if (arguments.length > 0) {
         store = model(update);
      }
      return store;
   };

   // Output
   const combined = Kefir
      .merge([model.skip(1), obs])
      .map(val => (store = val))   // Assignment
      .toProperty(() => store);

   for (let x in combined) getset[x] = combined[x];

   return getset;
};

Please feel free to point out any obvious problems here. I just started with these these and would like your feedback in spotting any issues.

Thanks

a-s-o commented 9 years ago

Usage

let day = Kefir.model('monday');
day('tuesday');
day.onValue(value => console.log(value));
day('wednesday');

// Logs
// => tuesday
// => wednesday

console.log( day() )       //=> 'wednesday'
day('thursday')            // set a value
console.log( day() )       //=> 'thursday'

Important properties:

  1. Maintains the latest value of the property when set through getter/setter interface (even if the resulting property is not active)
rpominov commented 9 years ago

I don't have any plans of implementing Model at the moment. I think it could be done as a separate lib/plugin.

Regarding your implementation, you shouldn't use private methods/properties that start from _. It's not part of the public API and can be changed at any time.

a-s-o commented 9 years ago

Okay, thanks for the info; that is good to know. I will keep updating the gist linked above further and hopefully someone else can make use of it.

In terms of the private _ methods, I need a way to arbitrarily push values down the underlying observable so that was the only way I was able to find out. Is there something similar to Bacon's bus.push() in Kefir or what would be the functional equivalent?

My other idea was to create a pool and then plug a single value stream into that each time a value is set on the model but that seems a little round-about to me and I also couldn't find an equivalent to bacon.once() in Kefir

Thanks

rpominov commented 9 years ago

In terms of the private _ methods, I need a way to arbitrarily push values down the underlying observable so that was the only way I was able to find out. Is there something similar to Bacon's bus.push() in Kefir or what would be the functional equivalent?

It was made a bit harder on purpose, because was considered an antipattern (see #88). But in your case it's ok to arbitrarily push values to a stream, and there are at least two ways of doing that: You can create a stream using Kefir.stream() and extract emitter from it as shown here. Also, as you mentioned, you can create a pool, and plug Kefrir.constant(x) to it.

P.S. I'll probably won't be able to answer to issues for a whille (until end of the month), but if you'll have other questions please keep posting, and I'll answer when be able to.

a-s-o commented 9 years ago

Okay, thanks for those references and your help. I will close the issue for now and post back if I have any other questions. I will update the original gist with a better implementation using the public methods as you suggested for future reference.