DoneJS StealJS jQuery++ FuncUnit DocumentJS
4.3.0
5.0.0 3.13.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-define
      • can-observation
      • can-observation-recorder
        • methods
          • add
          • addAll
          • created
          • ignore
          • isRecording
          • start
          • stop
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-fixture
      • can-fixture-socket
      • can-ndjson-stream
      • can-set
    • Views
      • can-component
      • can-stache
      • can-stache-bindings
      • can-stache-converters
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-nodelist
      • can-view-parser
      • can-view-scope
      • can-view-target
      • react-view-model
      • react-view-model/component
      • steal-stache
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
      • can-string
      • can-string-to-any
      • can-util
      • can-zone
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-define-validate-validatejs
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-observation-recorder

  • Edit on GitHub

Specify how to listen to changes in a value being read and record those specifications between two points in time.

Record observables being read and indicate how to listen to changes in a value being read.

Object

can-observation-recorder exports an object with the following methods:

{
    start()  // Starts recording ObservationRecorder.add calls and
             // returns an ObservationRecord representing
             // the ObservationRecorder.add calls.

    stop()   // Stops recording ObservationRecorder.add calls and
             // returns an ObservationRecord representing
             // the ObservationRecorder.add calls.

    add(obj, [key]) // Signal that an observable value was read.

    addMany(observations) // Add many observations at once

    ignore(fn) // Return a function that when called, prevents
               // Any ObservationRecorder.add calls from being noticed
}

can-observation-recorder serves two main purposes.

  • It allows observable values, when read, to specify how to listen to changes in the value being read.
    ObservationRecorder.add( map, "value" );
    
    ObservationRecorder.add( simpleValue );
    
  • Record all calls to ObservationRecorder.add between start and stop:
    ObservationRecorder.start();
    ObservationRecorder.add( person, "age" );
    ObservationRecorder.add( fullName );
    const observationRecord = ObservationRecorder.stop();
    observationRecord; //-> {
    //  keyDependencies:   Map{ person: Set[age] },
    //  valueDependencies: Set[fullName]
    //}
    

Use Cases

can-observation-recorder is primarily used in two ways:

  1. When any of CanJS's observable values (like can-define) are read, they MUST call add to let can-observation and other observable behaviors (react-view-model) know to bind to them.
  2. It is used by can-observation and other observables are able to track all the observables read within a function.

Specifying how to bind when a value is read

All of CanJS's observables MUST call add when an observable value is read. .add(observable [,key]) is called with arguments representing how to listen to when that value changes.

Depending if the observable represents a single value or key-value pairs, it will callback add with one or more values.

Key-value observables

If an object has observable key-value pairs, and a key was read through its .get method, it might implement .get as follows:

const observableKeyValues = {
    _data: {},
    get: function( key ) {
        ObservationRecorder.add( this, key );
        return this._data[ key ];
    }

    // ...
};

ObservationRecorder.add(observable, key) called with two arguments indicates that the observable argument's can.onKeyValue will be called with the key argument. In the case that observableKeyValues.get("prop") was called, can-observation would call:

observableKeyValues[ canSymbol.for( "can.onKeyValue" ) ]( "prop", updateObservation, "notify" );

So not only MUST observables call add, they also must have the corresponding observable symbols implemented. For observableKeyValues, this might look like:

const observableKeyValues = {

    // ...
    handlers: new KeyTree( [ Object, Object, Array ] ),
    [ canSymbol.for( "can.onKeyValue" ) ]: function( key, handler, queue ) {
        this.handlers.add( [ key, queue || "mutate", handler ] );
    },
    [ canSymbol.for( "can.offKeyValue" ) ]: function( key, handler, queue ) {
        this.handlers.delete( [ key, queue || "mutate", handler ] );
    }
};

can-event-queue has utilities that make this pattern easier. Also checkout can-key-tree and can-queues.

Single Value Observables

If an observable represents a single value, and that value is read through a .value property, it might implement that property as follows:

const observableValue = {
    _value: {},
    get value() {
        ObservationRecorder.add( this );
        return this._value;
    }

    // ...
};

ObservationRecorder.add(observable) called with one arguments indicates that the observable argument's can.onValue will be called. In the case that observableKeyValues.value was read, can-observation would call:

observableValue[ canSymbol.for( "can.onValue" ) ]( updateObservation, "notify" );

So not only MUST observables call add, they also must have the corresponding observable symbols implemented. For observableValue, this might look like:

const observableValue = {

    // ...
    handlers: new KeyTree( [ Object, Array ] ),
    [ canSymbol.for( "can.onValue" ) ]: function( handler, queue ) {
        this.handlers.add( [ queue || "mutate", handler ] );
    },
    [ canSymbol.for( "can.offValue" ) ]: function( handler, queue ) {
        this.handlers.delete( [ queue || "mutate", handler ] );
    }
};

can-event-queue has utilities that make this pattern easier. Also checkout can-key-tree and can-queues.

Tracking observables read between two points in time

One of CanJS's best features is that reading values broadcasts through ObservationRecorder.add how to listen to when those values changes. start and stop are used to capture that information, allowing utilities to listen to any values read between .start() and .stop().

This is sued by can-observation to make "computed" observables and by react-view-model to re-render and diff React's virtual DOM. Maybe you've got some cool uses too!

Use .start() and .stop() to track all .add(observation [,key] ) calls between two points as follows:

ObservationRecorder.start();

ObservationRecorder.add( observableKeyValues, "propA" );
ObservationRecorder.add( observableKeyValues, "propB" );
ObservationRecorder.add( map, "propC" );

ObservationRecorder.add( observableValue );
ObservationRecorder.add( fullNameCompute );

const observationRecord = ObservationRecorder.stop();

observationRecord would contain the following:

{
    keyDependencies: Map{
        [observableKeyValues]: Set["propA", "propB"],
        [map]: Set["propC"]
    },
    valueDependencies: Set[observableValue, fullNameCompute]
}

keyDependencies are kept in a Map that maps each observable to a set of the keys recorded for that observable. valueDependencies are kept in a Set.

start adds a new observation record to the stack each time it is called. Read its docs for more information about what this means.

How it works

can-observation-recorder is a stateful global. We should avoid breaking its API as much as possible.

It keeps a stack of observation records. .start() pushes to that stack. .add() adds values to the top of the stack. .stop() pops the stack and returns the former top of the stack.

How it works: can-observation and can-observation-recorder covers how can-observation-recorder works.

How it works: can-observation and can-observation-recorder covers how can-observation works.

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 4.3.0.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news