Giter VIP home page Giter VIP logo

introspected's Introduction

Introspected

Build Status Coverage Status donate

Medium presentation post


If you'd like to be notified about any possible change that could happen to a JSON compatible model / data / object / array / structure, including the possibility to retrieve the exact full path of the object that changed and eventually walk through it, you've reached your destination.

const data = Introspected(
  // any object or JSON compatible structure
  // even with nested properties, objects, arrays
  JSON.parse('{}'),
  (root, path) => {
    // the root object that changed
    console.log(root);
    // the path that just changed
    console.log(path);
  }
);

// now try the following in console
data.a.b.c.d.e.f.g = 'whatever';
data.array.value = [1, 2, 3];
data.array.value.push(4);
// see all notifications about all changes ๐ŸŽ‰

JSON.stringify(data);
// {"a":{"b":{"c":{"d":{"e":{"f":{"g":"whatever"}}}}}},"array":{"value":[1,2,3,4]}}

API

  • Introspected(objectOrArray[, callback]) create a new Introspected object capable of having infinite depth without ever throwing errors
  • Introspected.observe(objectOrArray, callback) crate a Introspected with a notifier per each change, or set a notifier per each change to an existent Introspected object
  • Introspected.pathValue(objectOrArray, path) walk through an object via a provided path. A path is an Array of properties, it is usually the one received through the notifier whenever a Introspected object is observed.

Compatibility

Any spec compliant ES2015 JavaScript engine.

(that means native WeakMap, Proxy and Symbol.toPrimitive too)

Live test page

Working: NodeJS 6+, Chrome, Safari, GNOME Web, Edge, Firefox, Samsung Internet (Chrome 51)

Not there yet: UC Browser (WebKit 534)

ISC License

introspected's People

Contributors

webreflection avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

introspected's Issues

Uncaught TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'a'

From MDN about the return value of delete:

true for all cases except when the property is an own non-configurable property, in which case, false is returned in non-strict mode.

It seems expected, but I don't know how to empty the state or if it is an anti-pattern.

"use strict";

const state = Introspected({
    a: 1,
    b: 2
});

for (let prop in state) {
  delete state[prop];
}  

// Uncaught TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'a'

Live demo: https://plnkr.co/edit/PuRseujyYxOGQbniXVZC?p=preview

0.2.0 Missing initial update

The example below displays an initial blank page and after a second the message "data updated".
It seems the initial update, rendering the message "data NOT updated", is not called.

"use strict";

// foo.template.js
class FooTemplate {
    static update(render, state) {
        render`<p> ${state.data} </p>`;
    }
}

// foo.controller.js
class FooController {
    constructor(render, template) {
        this.state = Introspected({
            data: "data NOT updated"
        }, state => template.update(render, state));

        setTimeout(() => {
            this.state.data = "data updated";
        }, 1000);
    }
}

// foo.component.js
class FooComponent {
    constructor() {
        const el = document.querySelector("foo");
        const render = hyperHTML.bind(el);

        this.fooController = new FooController(render, FooTemplate);
    }
}

// foo.module.js
new FooComponent();

Live example: https://plnkr.co/edit/JMQnl9qssBwxdj1ad3dp?p=preview

Passing bindings to a component

Imagine the need to pass something to a component (values, objects or functions): they are the bindings of that component.

In this primitive example of a modal dialog, I use a flag to show the modal and, when the modal is closed, some info is returned and the flag is set to false to hide the modal.

Notes:

  • I pass the whole state, as bindings, to the "child" component, and not a slice of the state: I need only openBarModal and closeBarModal.
  • I use observe api to add another handler to the state, rendering the "child" template, when the state (bindings) changes,

Concerns:

  • Do you think this bindings practice is feasible?
    I am trying to reproduce this kind of approach (in AngularJS):
<token-dialog open-modal="$ctrl.openTokenModal"
    close-modal="$ctrl.closeTokenDialog(tokenInfo)">
</token-dialog>
  • The dialog is not closed after pressing "Close Bar Dialog" button, even if state.openBarModal is set to false in onCloseBarModal.

The caller Foo component, in the template:

            <button id="openBarDialog" type="button"
                onclick="${() => {
                    state.openBarModal = true;
                }}">Open Bar Dialog
            </button>

and in the controller:

        this.state = Introspected({
            items,
            selectedItem: items[1],
            toggleList: true,
            openBarModal: false,
            closeBarModal: this.onCloseBarModal
        }, state => template.update(render, state, events));

        BarComponent.bootstrap(this.state);
...

    onCloseBarModal(res) {
        this.state.openBarModal = false;
        console.log(res);
    }

The callee Bar Component:

// bar.controller.js
class BarController {
    constructor(render, template, bindings) {
        Introspected.observe(bindings,
            state => template.update(render, state));
    }
}

// bar.component.js
class BarComponent {
    static bootstrap(bindings) {
        const render = hyperHTML.bind(Util.query("bar-dialog"));


        this.barController = new BarController(render, BarTemplate, bindings);
    }
}

// bar.template.js
class BarTemplate {
    static update(render, state) {
        if (!state.openBarModal) {
            return;
        }

        render`
...
                        <button id="closeBarDialog" type="button"
                            onclick="${() => {
                                state.closeBarModal({ answer: "BarModal closed" });
                            }}">Close Bar Dialog
                        </button>
...
  • How to reproduce the issue: press "Open Bar Dialog", the dialog is displayed, then press "Close Bar Dialog" and the dialog is not closed, even if onCloseBarModal is called correctly (there is the log in the console).

The complete live example: https://plnkr.co/edit/YwsHHSvjpDx1uXfQwTA5?p=preview

No notification about array element change

Hey!

I've discovered a bug - the library currently doesn't notice when you change an element of an array.

Test case:

const Introspected = require('introspected')

let arr = [{},2,3]
let changes = 0
let arr_intro = Introspected(arr, (value, path) => {
	changes += 1
})

console.log(changes)
arr_intro[0] = 3
console.log(changes)
arr_intro[2] = 4
console.log(changes)

Expected output:

1
2
3

Actual output:

1
1
1

Node version tested: v9.10.1

No changes detection using a reference

I have been playing with the component example trying to separate the concerns in terms of model, view and controller.

Below a part of the state is created in a service and the reference is passed to the Introspected object.

Later the state is updated, but the Introspected callback is not triggered.
fooService.getItems() returns four items, foo.state.items contains the initial three items.

Have I been missing anything about Proxy?

class FooService {
    constructor() {
        this.items = [
            { id: 1, text: "foo" },
            { id: 2, text: "bar" },
            { id: 3, text: "baz" }
        ];

        setTimeout(() => { // simulating an async update of the model
            this.items.push({ id: 4, text: "minions" });
        }, 3000);
    }

    getItems() {
        return this.items;
    }
}

class FooController {
    constructor(root, fooService) {
        this.render = hyperHTML.bind(root);

        this.state = Introspected(
            {
                items: fooService.getItems()
            }, state => FooTemplate.update(
                this.render,
                state,
                events => this.handleEvent(events)
            )
        );

        this.state.selectedItem = this.state.items[0];
    }
...

The complete example: https://plnkr.co/edit/EZ8sMc7uJMgKmsvg0D8k?p=preview

How to render the template only if a sub-property of state is updated?

I am trying to update a template using a sub-property of another state.

"use strict";

const render = hyperHTML.bind(document.querySelector("root"));
const render2 = hyperHTML.bind(document.querySelector("root2"));
const render3 = hyperHTML.bind(document.querySelector("root3"));

// The usual state
const state = Introspected({
    catalogName: "Default Catalog",
    items: ["item1", "item2"]
}, s => {
    render`<h1>${s.catalogName}</h1>
        <ul>${s.items.map(item => `<li> ${item} </li>`)}</ul>
    `;
});

// Add another rendering to the same state
Introspected.observe(state, s => {
    render2`<h1>Another ${s.catalogName}</h1>
        <ul>${s.items.map(item => `<li> ${item} </li>`)}</ul>
    `;
});

setTimeout(() => {
    state.catalogName = "Default Catalog Updated"; // updating only root1, root2
}, 1000);

// Trying to add another rendering to a partial state,
// intending to update the template only if state.items change
Introspected.observe(state.items, s => {
    render3`<h1>Other Catalog</h1>
        <ul>${s.items.map(item => `<li> ${item} </li>`)}</ul>
    `;
});

setTimeout(() => {
    state.items.push("item3"); // updating root1, root2 and root3
}, 1000);

Live demo: https://plnkr.co/edit/IjZF042kY84L3fOGASe3?p=preview

Feature request: read access callback

We have some C++ code that serializes our objects via an Archive class to either XML or JSON.
Because JSON is new to the Archive serializer it wasn't a concern when most of our object serialization code was written.

The problem now is:
If the object has an empty array property we do not generate any JSON for it.
If the object has at least one element in this array property it generates a JSON array with the proper elements.

C++ Example Code:

const bool generate_array = true;
for( auto e : container ){
	Archive.BeginElement( "my_element", generate_array );
	// ... element content
	Archive.EndElement();
}

Resulting JSON for an empty array property:

{
}

Resulting JSON for an array property filled with one number:

{
	"my_element": [ 666 ]
}

Using such an object on the JS side is rather tedious, because accessing the array property may result in errors if the array was empty on C++ side. You can work around that by adding empty array properties if the property doesn't exist. But that's error prone and may be forgotten by the programmer.

Therefore I thought of adding an empty array using Introspected once an undefined property is accessed. By now I only achieved this by editing the Introspected code itself replacing the "default" code path in the get() function.

Is this a valid use case? Do I miss some obvious point in the API? Or could this functionality be added to the library?

Uncaught TypeError: target.toJSON is not a function

I simplified a use case I have in my code.

From the console of the live test page:

x = Introspected({arr: []})
Proxy {arr: Array(0)}

Introspected.observe(x.arr, () => {})
Uncaught TypeError: target.toJSON is not a function
    at Function.Introspected [as observe] (introspected.js:179)
    at <anonymous>:1:14

Introspected.observe(x.arr)
[copyWithin: ฦ’, fill: ฦ’, pop: ฦ’, push: ฦ’, reverse: ฦ’,ย โ€ฆ]

Is it expected? Am I missing anything?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.