gullerya / object-observer Goto Github PK
View Code? Open in Web Editor NEWObject Observer functionality of JavaScript objects/arrays via native Proxy
License: ISC License
Object Observer functionality of JavaScript objects/arrays via native Proxy
License: ISC License
Hi!
I've been through the code in order to use this as a dependency for one of my vanilla JS project and I've seen that the method Observable.isObservable
uses some typeof checks to verify that the object is observable.
I think that it might be better to use Symbols like the Array.isArray
method. To be more precise, there is an article that goes deep on the advantages to use Symbols and how they are used in javascript is
methods here.
Influenced by this article, I'd like to move away from default export of an API to the explicitly named one.
In ES6 flavor of library usage today, one may do the following:
import Observable from '../dist/module/object-observer.js'
This is quite good, I'd say.
But others would follow some other convention of naming objects after the file name and would do:
import ObjectObserver
from '../dist/module/object-observer.js'`
This is quite a miss from APIs visibility and clarity point of view.
When objects being replaced on the observed graph, old object and all of its subgraph is being revoked and new object becomes cloned and observable.
In case when new value is child of an old value, in the callback the new value appears to be non-usable revoked proxy. See flow below.
This affects callbacks only, the values on the observed graph itself are okay.
let g = {
a: {
b: {
prop: 'text'
},
prop: 'text'
}
};
let og = Observable.from(g);
og.observe(changes => {
changes[0].value.prop === 'text'; // at this point an error is thrown
});
og.a = og.a.b;
og.a.prop === 'text'; // this is fine, only the value within the callback is affected
As of now the script that is releasing the version extracts the version from the tag.
Chore proposals:
I would like to try this library, but I can't get past loading. Even the fiddle linked doesn't work at all ("import a reserved word"). I have tried many permutations of require, import, etc, but am getting errors about modules.
Is there a working fiddle for this?
it might be useful to provide an immediate object that the change was performed on
thus, even when the path of the change is a path from the base Observable
, the last node in the path will be the property / index on the provided object
- an immediate subject of the change in a nested observable graph
consider adding the object
property to the change event, similar to the one in the Object.observe
original spec (nowadays - deprecated)
The call of the files is not working properly.
let Observable = require('./dist/node/object-observer').Observable;
I had to replace it with
let Observable = require('./node_modules/object-observer/dist/node/object-observer').Observable;
And now that I managed to load the library I have an error
/node_modules/object-observer/dist/node/object-observer.js:632
export { Observable };
^^^^^^
SyntaxError: Unexpected token export
at new Script (vm.js:79:7)
at createScript (vm.js:251:10)
at Object.runInThisContext (vm.js:303:10)
at Module._compile (internal/modules/cjs/loader.js:657:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
As of now Observable
API behaves as following:
Observable
from what to observeObservable
a logic of how (callback, the actual logic) furtherI'd like to explore a possibility to add another, more DOM-like flavor to the API:
ObjectObserver
with the callback - the how onceThe second API flavor is more resonating with the APIs like MutationObserver
and ResizeObserver
.
It would also be a good extensibility exercise for the library to see if it's capable of such an enhancements.
As of now, TypedArrays are not treated as Arrays actually, but as a regular object.
This would eventually break the logic at some point, since when these ones are getting pre-processed during the observification process, they are turned to a regular objects and loosing their special TypedArray methods.
As of now, if by any chance a consumer gets a nested object from an Observable
path, it is not possible to just start observing that object.
In fact it is even not possible to know that it is part of on already Observable
graph.
Proposal: make it possible to observe the nested parts of the Observable
just as it would be a root. It should be quite an easy, since internally object-observer
may understand that the object is part of an Observable
and just set the observe callbacks to react on the paths from that point and below.
Issues:
observe
, unobserve
properties not only on the Observable
root, but on any nested object as wellObservable
so the revokation (inability of that) should also be transparent (some message to console)?As of now, from the documentation it is not clear how to observe a properties of the object as of the root only.
Need to improve the documentation.
It may be useful to be able to 'observe' calculated fields. For example:
let user = {
firstName: 'Some',
lastName: 'Name',
get fullName() {
return this.firstName + ' ' + this.lastName;
}
};
It is obviously possible to observe the firstName
and the lastName
. But it would be cool to tell ObjectObserver
, that fullName
is dependent on those two and each time one of them being changed - check the newly calculated value of the fullName
and if changed - dispatch event for the fullName
change as well.
Things that are still not clear to me:
this is not a issue. But not sure where to ask so putting it up here.
Based on the example:
let order = { type: 'book', pid: 102, ammount: 5, remark: 'remove me' },
observableOrder = Observable.from(order);
observableOrder.ammount = 7;
I need to submit observableOrder to the backend.
observableOrder should be
{ type: 'book', pid: 102, ammount: 7, remark: 'remove me' }
But when I
console.log(observableOrder);
I get the following
Proxy {amount: 7, pid: 102, type: "book", address: Proxy, revoke: ƒ, …}
[[Handler]]: ObjectObserver
isRevoked: false
proxy: Proxy {amount: 7, pid: 102, type: "book", address: Proxy, revoke: ƒ, …}
revokable: {proxy: Proxy, revoke: ƒ}
target: {amount: 7, pid: 102, type: "book", address: Proxy, revoke: ƒ, …}
observers: Map(1) {ƒ => {…}}
__proto__: ObserverBase
[[Target]]: Object
address: Proxy {apt: 30, street: "Str 75", Symbol(system-observer-key): ObjectObserver}
amount: 7
pid: 102
type: "book"
Symbol(system-observer-key): ObjectObserver {isRevoked: false, revokable: {…}, proxy: Proxy, target: {…}, observers: Map(1)}
observe: ƒ value(observer, options)
revoke: ƒ value()
unobserve: ƒ value()
__proto__: Object
[[IsRevoked]]: false
How Can I get the original value back without the attached properties?
When a nested array is sort()
ed or reverse()
d, the library generates a shuffle
or reverse
event, respectively. These events do not include a path and it is therefore not possible to distinguish which internal array has been modified. For example:
> const Observe = require('object-observer');
> const target = Observe.from({foo: [1, 3, 2], bar: [7, 9, 8]});
> target.observe(c => { console.log(c); });
> target.foo.sort();
[ { type: 'shuffle' } ]
Ideally these events would come with a path
field, just like other sorts of events:
> target.foo.sort();
[ { type: 'shuffle', path: ['foo'] } ]
hi , thanks for the library.
the demo link provided in jsfiddle
is not working.
ReferenceError: Observable is not defined
When observing object having non-observable property, and this property is being removed/replaced - ObjectObserver should not attempt to revoke a proxy of an old value, it's simply failing on undefined since the proxy is not exists.
Reproduce:
let o = {
test: Promise.resolve()
},
oo = Observable.from(o);
// for this flow we must have a callbacks wired to the observable
oo.observe(function() {});
oo.test = null; // this line will throw exception
As of 4.1.3
object-observer
is available via CDN.
Document it.
As of now, the performance benchmarks are running for the console, the results are not really asserted.
I should have some reasonable thresholds set and assured as to be able to clearly see the degrade if any.
Those thresholds will also become a performance waterline mark.
This lib is great!
Could you add the ability to do the following:
let user = {
firstName: 'Aya',
lastName: 'Guller',
phone: {
number: '023887942'
},
address: {
city: 'of mountaineers',
street: 'of the top ridges',
block: 123,
extra: {
data: {}
}
}
},
oUser = Observable.from(user);
oUser.observe(callback, {paths: [ 'firstName', 'address' ]});
oUser.observe(callback, {pathsOf: [ 'phone', 'address.extra' ]});
oUser.observe(callback, {pathsFrom: [ 'phone', 'address' ]});
They'd function the same way as their counterparts but only that we are observing multiple paths.
As of now, setting of observable object property to an even strictly equal value causes a change event to be fired:
let o = Observable.from({ p: true });
o.p = true; // this will fire an update event
object-observer
should NOT fire events when strictly equal primitives are involved in the change
The following behavior is malfunctioning:
let a = [{text: 'a'}, {text: 'b'}, {text: 'c'}, {text: 'd'}];
let pa = Observable.from(a);
...
pa.splice(1, 2, {text: '1'}, {text: '2'}, {text: '3'});
// now array will looks like this: [{text: 'a'}, {text: '1'}, {text: '2'}, {text: '3'}, {text: 'd'}]
From this point on, changes on the last item ({text: 'd'}
) should deliver the changes with path 4
.
But the changes are delivered with path 3
.
Last item's path should be reindexed correctly according to the newly pushed items (array expansion).
isObservable
should return true
only when the root of an observable graph is checked, not the children of it.
As of now, children are also considered to be observables - wrongly.
When observing is set for a specific path/s, it was found that after filtering out non-relevant changes and when actually no relevant changes left, the callbacks were still called with an empty changes array.
This is not convenient at least and contradicts that API that says that callbacks will ever be called with a non-empty array.
As per present state of expected behaviour, keys iteration on ES6 objects should be predictable and consistent.
It seems, that during the cloning, object-observer
reverses the order of the keys, which breaks some of the flows that are dependent on that order.
For more info read here.
Hi,
I'm using your library with minor tweak. I've added support for prototype chain preservation.
Solution is simple. Add target.__proto__ = source.__proto__;
on line 100.
Some additions to API are needed to observe a specific object path.
In example, I've an object like:
{
a: {
b: {
c: 'value'
}
}
}
I would like to subscribe only to "a.b.c." updates (even if it doesn't exist yet), not to any update in object.
This is more about code correctness, there is no need for special handling of Error's base class deviation, all of them should be handled as one
It would be nice to have an option where the observable object is prevented from new properties being added , and existing properties being removed (similar behavior to Object.seal() )
This will serve the use case where we only want to change/mutate the existing properties (e.g. application state managements, etc.).
Thank you!
I have a suspect, that when observing the root change of an array (pathsOf: ''
) the events of an array mutations like reverse or shuffle are NOT being dispatched.
if you do this with standard object:
var t={a:{},b:{}}
undefined
t.a.c='i'
"i"
t.b=t.a
{c: "i"}
t.a.c
"i"
t.b.c
"i"
t.a.c='o'
"o"
t.a.c
"o"
t.b.c
"o"
with your object observer you will see that t.b.c. will be "i".
Because when you do t.b=t.a you clone t.a.
I think in this case we don't need to clone but to add another parent to the observer.
It will complicate a lot the code but it will be as close as possible to a standard js object
Thank you.
ps I think your work is huge
ps2 revoke oldvalue after deliver the changes because if newvalue is a child of oldvalue it will throw an error
When object removed (deleted or replaces) from an Observable tree, it should be looked up for it's proxy and if there is any - it should be revoked.
Currently revoke logic is invoked only when there are callbacks wired to this observable.
Should it be possible to specify an async callbacks flavor?
If yes, shall I leverage an EventTarget
object?
Thank you for this excellent library.
My scenario is that I'm using Object.assign
, which is (correctly) invoking the callback for each implicit assignment. So if I have an object with two properties, the callback is invoked twice.
However, from the application standpoint, this is one change. Is there some way to detect this? Or group the changes? The argument to the callback is an array, but always appears to have just one value.
I'm guessing (I have not studied the code) that this is just a set trap which then invokes the callback. I don't have any idea how you might group changes together. But I thought perhaps this might be something to which you've given some thought. Is there a way to do this? If not, is there a good workaround?
As of now, objects removed from an observed graph are being revoked.
It is not something that we MUST have, actually I've opted it in just as a hardening API feature, not much of thought was invested here.
Now, when subgraphs may function on their own as an observables, it is actually a breaking thing, since some consumer may get a reference to the subgraph and observe or just use it, and enocounter erroneous situation is somewhere upper the chain the obect was detached from the observable graph.
Suggestion - revokation should be OFF by default (leave some feasable option to opt it in once this feature will be required).
Add support to copyWithin
array's method.
As of now, it seems to me that this functionality should be treated same as fill
or splice
from the detailing perspective.
Hello,
I was wondering if there was an easy way to access the the original object from just the proxy. I'm looking for the actual object reference, not just a copy.
If not, would it be possible to add a function that would work as follows:
const order = { type: 'book', pid: 102, ammount: 5, remark: 'remove me' };
const observableOrder = Observable.from(order);
const originalOrder = Observable.original(observableOrder);
order === originalOrder // true
Hi, great work on the library! I'm currently using it for another project but would really love the ability to enable a "get" trap for any deep getters triggered.
I imagine this would probably be best done as an option to .observe
and turned off by default, as most people would be interested in setters. However by adding this trap you have even more ability with this library to observe deep object usage.
Would you consider adding this? I was considering forking it but of course being the author you would get this done faster and without chance of regression or bugs.
I also use TypeScript and have a definition file I made by hand.
declare module 'object-observer' {
export interface Change {
type: 'insert' | 'update' | 'delete' | 'shuffle' | 'reverse';
path: Array<string | number>;
value: any;
oldValue: any;
object: any;
}
export type ChangesHandler = (changes: Change[]) => void;
export interface Options {
path?: string;
pathsOf?: string;
pathsFrom?: string'
}
export class Observable {
static from(obj: any): Observable;
static isObservable(obj: any): boolean;
observe(callback: ChangesHandler, options?: Options): void;
unobserve(): void;
}
}
Feel free to modify it, or submit it to @types/object-observer
.
Thanks again!
Ali
I'd like to consider to publish the fully workable version of the lib to CDN.
Add it to the documentation for an easy consumption and trials.
Since NodeJS 14 is already in ACTIVE mode and soon will be going to MAINTENANCE, both of which are a suggested modes for production use, and since NodeJS 14 provides ES6 modules supports OOTB, I shall remove the special distro and all related build steps for the older CommonJS 'require' syntax.
nested observable should
Hi
Thank you for this package - I'm hoping it's going to solve my problem but I'm unable to observe a change.
Initially I set up an observer within a class to another class - I want to look for an update to a property and then take action. Changes to the property were made but not change logged. Does this code recognise changes within a class syntax?
I then decided to create an external object that is updated from the class I was observing and observe that instead but again nothing noted when the values are updated.
I'm doing something wrong
my bare bones code:
import { Observable } from 'object-observer/dist/object-observer.js';
import {selectedAsset} from "./filemanager-ha
export let fileImageWidgetControls = class {
openFileManager = () => {
const filemanager = new filemanagerHandler({
images:true
});
filemanager.init();
let filemanagerObserver = Observable.from(selectedAsset);
filemanagerObserver.observe(changes => {
changes.forEach(change => {
console.log("filemanager change:", change);
});
});
}
export let filemanagerHandler = class {
handleSelection = (element, type, callback) => {
selectedAsset = {
filename: element.attr('href'),
id: element.data('id'),
caption: element.data('caption'),
type: type
}
console.log("selected:", selectedAsset);
}
}
export let selectedAsset = {
}
so the first class interacts with the second class that after some user action updates selectedAsset
but no changes notified
console logging suggests selectedAsset
is being updated as expected
Any help appreciated
Hi,
great library there, after making some tests, all is working great, except no support for the for mentioned new ES6 Data Types, is there a possibility to support them?
thanks in advanced.
There is a use case, when one would like to observe the changes on the root level only.
It's not possible today, since the API rejects an empty string in the { path: '<path>' }
option.
Should be allowed.
Hello,
For some reason i am starting getting this error, this module used to work fine but now i am not able to work with it... Is there a fix?
let observ = require('./object-observer/dist/node/object-observer');
Warning: require() of ES modules is not supported.
require() of C:\Users\user\Desktop\avrorajs\bin\object-observer\dist\node\object-observer.js from C:\Users\user\Desktop\project\bin\bundle.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename object-observer.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from C:\Users\user\Desktop\project\bin\object-observer\package.json.
Makes it easier.. I guess this is just a suggestion, but trying out if something works for me is a lot of work when you look for libraries.
I tried hooking your library up to some dom elements somehow, not sure how to do it:
http://jsfiddle.net/djthkd6L/1
for example in your sample:
var order = { type: 'book', pid: 102, ammount: 5, remark: 'remove me' },
observableOrder;
observableOrder = Observable.from(order);
observableOrder.observe(changes => {
changes.forEach(change => {
console.log(change);
});
});
observableOrder.ammount = 7; // { type: 'update', path: ['ammount'], value: 7, oldValue: 5 }
observableOrder.address = { // { type: "insert", path: ['address'], value: { ... } }
street: 'Str 75',
apt: 29
};
observableOrder.address.apt = 30; // { type: "update", path: ['address','apt'], value: 30, oldValue: 29 }
delete observableOrder.remark; // { type: "delete", path: ['remark'], oldValue: 'remove me' }
this wont actually update the order
object... and the change events dont even tell me which object was the original object, which I assume I'd have to update myself during the callback? Is that correct?
still leaves open how to make all this work with html elements
Problem: TypeError: Cannot perform 'get' on a proxy that has been revoked thrown in Object.assign.
minimal code:
var oldData = { b : {b1 : "x", b2 : "y" } };
var newData = { b : { b1: "z" } };
var observableData = require("object-observer").Observable.from(oldData);
observableData.observe(dataObserver);
Object.assign(observableData, newData);
function dataObserver(changes) {
changes.forEach(change => {
console.log(change.type + " of " + change.path + " from " + change.value + " to " + change.oldValue);
});
};
Proposal:
I'm javascript newbie, so no proposals. Error might be on my side.
Relevant version/s (if any): 0.2.4
(node v8.9.4)
When processing graph/subgraph, the native JS object like Date, Number etc should be excluded since there is not much value in proxifying them, yet there is some functonality break at special cases
I am using this module most of my time on the server side, i guess there was an update to this module and i started to getting new errors, here is one of them:
} else if (target instanceof Date || target instanceof Blob || target instanceof Error) {
^
ReferenceError: Blob is not defined
The blob class is not part of the nodejs so the module throwing the error.
To fix it i installed cross-blob package to make it work in nodejs. I hope it will help to someone!
Non ES6 flavor of library consumption is still here, as you can see from the readme
docs.
Active support and development of the features in this mode is already mostly not happening, only defect fixes.
As the community runs fast toward ES6 scripting and import/export
are everywhere and supported by a vast majority of build/transpile tools, I'd like to remove the support of the non-ES6 flavor (the one that you should do the reference to the script from HTML) whatsoever.
Attention! ES6 like imports will be slightly impacted as well - I'll bring them from being under ../dist/module
folder to by under ../dist
folder directly.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.