Closed gajewsk2 closed 6 years ago
You have to go through methods like you're talking about. So in your child iframe you might have something like this:
const childModel = {
max: 5000
};
const connection = Penpal.connectToParent({
methods: {
get(name) {
return childModel[name];
}
});
And then from the parent:
connection.promise.then(child => child.get('max').then(max => console.log(max)));
Cool, gets the job done. Would it be possible to expose properties as well in a future update? Or does that go against Penpal's design somehow?
It kind of goes against the design. Penpal's primary goal is communication (for which methods are used). Whether you have data models or how you might structure them is outside of its scope.
Something that might be helpful to understand (if you don't already) is that the child object that you get here:
connection.promise.then(child => child.get('max').then(max => console.log(max)));
isn't any actual object that resides inside your iframe. Basically, when the iframe connects to the parent window, it collects any keys that are on the methods
object and passes them to the parent window. The parent window then reconstructs an object with the same keys and makes some proxy methods for their values. This object serves as a proxy and becomes your child
object in the snippet above. As such, child
isn't a "live" object. For example, if you add or remove properties (or changed their values) on the methods
object here:
const connection = Penpal.connectToParent({
methods: {
get(name) {
return childModel[name];
}
});
after connectToParent
has been called, the child
object that's in the parent wouldn't be updated.
Even though the concept of exposing properties is against the design, I'd still like to know more about how you're thinking such a mechanism would work. If you changed max
from the iframe, would you expect the new value to be sent to the parent window to update child.max
? If so, you may run into a race condition because iframe communication is asynchronous. In other words, if you tried to access child.max
from the parent window at about the same time max
is updated from within the iframe, you may not be getting the actual, current value that's been set from within the iframe.
My view of Penpal was as a more robust postmessage. I use postmessage to pass primitive or static configuration objects one way typically. I wouldn't expect the two way binding to persist for initial connection properties.
EDIT: The interaction i'm talking about it only for properties from initial connections. I could use PostMate for something like that or just the builtin postmessage, just hoping for something more elegant.
You can pass primitive or static configuration objects back and forth. What I've shown in my original response is a pull methodology. If you want a push methodology (more like postMessage) rather than a pull, you can do so:
From the child:
connection.promise.then(parent => {
parent.onChildData(childData);
});
From the parent:
const connection = Penpal.connectToChild({
...
methods: {
onChildData(data) {
console.log('child data', data);
}
}
});
@gajewsk2 Do you have a proposal for what the API would look like in Penpal that would satisfy what you're looking for?
I'm thinking my ideal api would allow any property type and not have a methods property by default: From Child
const connection = Penpal.connectToParent({
get: function(name) {
return childModel[name];
},
someMethod: function(blah){return foo;},
config:{
parentOrigin: '*',
debug: false,
promise: Promise
},
someObj: {
oneAndDone: 123,
iWontUpdateWithoutAnotherConnnection: 'default'
}
});
From Parent:
connection.promise.then(parent => {
// I've already gotten an initial response,
//according to the childs signature I can now access someObj syncronously
console.log(parent.someObj.oneAndDone);
// methods will continue with usual Penpal async style
parent.someMethod(parent.someObj).then((foo)=>console.log(foo));
});
I realize users could put methods inside of objects so to support them you would have to go through the nested objects and detect if their type is a function and then Penpal-ify it, but I think this sort of structure makes it feel like you are using objects in a very natural JS way.
Thoughts on this?
I like the configuration use case, such as using debug
to tell a child to operate in debug mode.
IMHO the most common mistake with @gajewsk2's proposal would be someone expects Penpal to create a proxy object instead of freezing the property.
var object = {};
object.debug: true;
object.getDebug: function(){ return object.debug; };
Penpal.connectToParent(object);
object.debug = false;
// Later
child.getDebug().then(debug => debug === child.debug) // Why aren't they equal?
child.debug.get().then(...) // OR! should we provide async getters for props so that mutations can go through
A couple suggestions here:
Penpal.connectToParent(methods,metadata)
. Would help clarify the difference between the static frozen properties and the dynamic methods. Access via parent._metadata.debug
.Object.freeze
to freeze the properties.Aside
For Parent->Child communication the fastest approach wouldn't used PostMessage at all, it would use query params or hash params. That way if you were trying to get the Client to debug, it would happen immediately instead of only after the handshake is complete. I'm not suggesting we do this, just reminding about speediness of passing configuration details.
I'm interested in separating methods and metadata - I think it's a valid concern and thought. And I've personally never seen a valid justification of the speed impact for Object.freeze
, but that aside, it certainly would make it clear to the user.
To your side comment, I'm quite familiar with using query params on iframes for these sorts of things, but what do you mean by hash params in this context?
I have the same concerns that @loganvolkers expressed. I'm open to considering an extra metadata param (or something similar) so that it's more clear that the object is not going to be kept up to date after it's sent to other window. Using parent._metadata
to access it feels quirky. I don't really like any of the alternatives I can think of either though.
I'd be curious to hear about use cases (other than debug
) that this metadata would be useful for where the current design doesn't work well. In my usage, I've just sent initial metadata in a call right after the connection is established. Something like:
connection.promise.then(child => {
child.setMetadata(someMetadata);
});
At the moment, I'm not convinced that the value this would bring to the library would outweigh the cost of a more complex API. Closing.
It seems like anything you want to pass over the postmessage, you just put in methods. But what if you want to pass over static properties, like
'max': 5000
. Obviously you could use a getter-ish method and slap it in methods, but I'm wondering if that's really the best way, or if there could be some way to access properties immediately from theconnection.promise
's resonse? eg