Skip to content

Commit

Permalink
Add xprops.getSiblings
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Brain committed Sep 10, 2021
1 parent 579e3ba commit 4c0ae56
Show file tree
Hide file tree
Showing 7 changed files with 1,039 additions and 134 deletions.
24 changes: 24 additions & 0 deletions docs/api/xprops.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ Unique ID for the component instance
console.log('The current component uid is:', window.xprops.uid);
```

## xprops.tag `string`

Tag for the component instance

```javascript
console.log('The current component is:', window.xprops.tag);
```

## xprops.getParent `() => Window`

Get a reference to the parent window
Expand Down Expand Up @@ -253,4 +261,20 @@ window.xprops.parent.export({
console.log('hello world!')
}
});
```

## xprops.getSiblings `({ anyParent : boolean }) => Array<{ tag : string, xprops : XProps, exports : Exports }>`

Get an array of sibling components on the same domain

```javascript
for (const sibling of window.xprops.getSiblings()) {
console.log ('Found sibling!', sibling.tag);
}
```

```javascript
for (const sibling of window.xprops.getSiblings({ anyParent: true })) {
console.log ('Found sibling from any parent!', sibling.tag);
}
```
78 changes: 63 additions & 15 deletions src/child/child.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

import { isSameDomain, matchDomain, getDomain, getOpener,
getNthParentFromTop, getAncestor, getAllFramesInWindow,
type CrossDomainWindowType, onCloseWindow } from 'cross-domain-utils/src';
type CrossDomainWindowType, onCloseWindow, assertSameDomain } from 'cross-domain-utils/src';
import { markWindowKnown, deserializeMessage, type CrossDomainFunctionType } from 'post-robot/src';
import { ZalgoPromise } from 'zalgo-promise/src';
import { extend, onResize, elementReady, assertExists, noop } from 'belter/src';

import { getGlobal } from '../lib';
import { CONTEXT, INITIAL_PROPS, WINDOW_REFERENCES } from '../constants';
import type { NormalizedComponentOptionsType } from '../component';
import type { PropsType } from '../component/props';
import type { NormalizedComponentOptionsType, getSiblingsPropType } from '../component';
import type { PropsType, ChildPropsType } from '../component/props';
import type { WindowRef, PropRef, ParentExportsType } from '../parent';
import type { StringMatcherType } from '../types';

Expand All @@ -25,6 +25,7 @@ export type ChildExportsType<P> = {|

export type ChildHelpers<P, X> = {|
uid : string,
tag : string,
close : () => ZalgoPromise<void>,
focus : () => ZalgoPromise<void>,
resize : ({| width : ?number, height : ?number |}) => ZalgoPromise<void>,
Expand All @@ -34,7 +35,8 @@ export type ChildHelpers<P, X> = {|
getParentDomain : () => string,
show : () => ZalgoPromise<void>,
hide : () => ZalgoPromise<void>,
export : (X) => ZalgoPromise<void>
export : (X) => ZalgoPromise<void>,
getSiblings : getSiblingsPropType
|};

function getParentComponentWindow(ref : WindowRef) : CrossDomainWindowType {
Expand Down Expand Up @@ -86,18 +88,18 @@ function destroy() : ZalgoPromise<void> {
});
}

function getPropsByRef<P>(parentComponentWindow : CrossDomainWindowType, domain : string, { type, value, uid } : PropRef) : (PropsType<P>) {
function getPropsByRef<P>(parentComponentWindow : CrossDomainWindowType, domain : string, propRef : PropRef) : (PropsType<P>) {
let props;

if (type === INITIAL_PROPS.RAW) {
props = value;
} else if (type === INITIAL_PROPS.UID) {
if (propRef.type === INITIAL_PROPS.RAW) {
props = propRef.value;
} else if (propRef.type === INITIAL_PROPS.UID) {
if (!isSameDomain(parentComponentWindow)) {
throw new Error(`Parent component window is on a different domain - expected ${ getDomain() } - can not retrieve props`);
}

const global = getGlobal(parentComponentWindow);
props = assertExists('props', global && global.props[uid]);
props = assertExists('props', global && global.props[propRef.uid]);
}

if (!props) {
Expand All @@ -107,17 +109,18 @@ function getPropsByRef<P>(parentComponentWindow : CrossDomainWindowType, domain
return deserializeMessage(parentComponentWindow, domain, props);
}

export type ChildComponent<P> = {|
getProps : () => PropsType<P>,
export type ChildComponent<P, X> = {|
getProps : () => ChildPropsType<P, X>,
init : () => ZalgoPromise<void>
|};

export function childComponent<P, X, C>(options : NormalizedComponentOptionsType<P, X, C>) : ChildComponent<P> {
const { propsDef, autoResize, allowedParentDomains } = options;
export function childComponent<P, X, C>(options : NormalizedComponentOptionsType<P, X, C>) : ChildComponent<P, X> {
const { tag, propsDef, autoResize, allowedParentDomains } = options;

const onPropHandlers = [];
const childPayload = getChildPayload();
let props : PropsType<P>;
let props : ChildPropsType<P, X>;
const exportsPromise = new ZalgoPromise();

if (!childPayload) {
throw new Error(`No child payload found`);
Expand Down Expand Up @@ -155,12 +158,53 @@ export function childComponent<P, X, C>(options : NormalizedComponentOptionsType
};

const xport = (xports : X) : ZalgoPromise<void> => {
exportsPromise.resolve(xports);
return parent.export(xports);
};

const getSiblings = ({ anyParent } = {}) => {
const result = [];
const currentParent = props.parent;

if (typeof anyParent === 'undefined') {
anyParent = !currentParent;
}

if (!anyParent && !currentParent) {
throw new Error(`No parent found for ${ tag } child`);
}

for (const win of getAllFramesInWindow(window)) {
if (!isSameDomain(win)) {
continue;
}

const xprops : ChildPropsType<mixed, mixed> = assertSameDomain(win).xprops;

if (!xprops || getParent() !== xprops.getParent()) {
continue;
}

const winParent = xprops.parent;

if (!anyParent && currentParent) {
if (!winParent || winParent.uid !== currentParent.uid) {
continue;
}
}

result.push({
props: xprops,
exports: getGlobal(win).exports
});
}

return result;
};

const getHelpers = () : ChildHelpers<P, X> => {
return {
show, hide, close, focus, onError, resize,
tag, show, hide, close, focus, onError, resize, getSiblings,
onProps, getParent, getParentDomain, uid, export: xport
};
};
Expand Down Expand Up @@ -222,6 +266,10 @@ export function childComponent<P, X, C>(options : NormalizedComponentOptionsType

const init = () => {
return ZalgoPromise.try(() => {
getGlobal().exports = options.exports({
getExports: () => exportsPromise
});

checkParentDomain(allowedParentDomains, parentDomain);
markWindowKnown(parentComponentWindow);
watchForClose();
Expand Down
8 changes: 4 additions & 4 deletions src/child/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { getDomain, isSameDomain, type CrossDomainWindowType } from 'cross-domain-utils/src';

import type { PropsDefinitionType, PropsType } from '../component/props';
import type { PropsDefinitionType, PropsType, ChildPropsType } from '../component/props';

import type { ChildHelpers } from './index';

Expand All @@ -15,8 +15,8 @@ export function normalizeChildProp<P, T, X>(propsDef : PropsDefinitionType<P, X>
const prop = propsDef[key];

if (typeof prop.childDecorate === 'function') {
const { uid, close, focus, onError, onProps, resize, getParent, getParentDomain, show, hide, export: xport } = helpers;
const decoratedValue = prop.childDecorate({ value, uid, close, focus, onError, onProps, resize, getParent, getParentDomain, show, hide, export: xport });
const { uid, tag, close, focus, onError, onProps, resize, getParent, getParentDomain, show, hide, export: xport, getSiblings } = helpers;
const decoratedValue = prop.childDecorate({ value, uid, tag, close, focus, onError, onProps, resize, getParent, getParentDomain, show, hide, export: xport, getSiblings });

// $FlowFixMe
return decoratedValue;
Expand All @@ -26,7 +26,7 @@ export function normalizeChildProp<P, T, X>(propsDef : PropsDefinitionType<P, X>
}

// eslint-disable-next-line max-params
export function normalizeChildProps<P, X>(parentComponentWindow : CrossDomainWindowType, propsDef : PropsDefinitionType<P, X>, props : PropsType<P>, origin : string, helpers : ChildHelpers<P, X>, isUpdate : boolean = false) : (PropsType<P>) {
export function normalizeChildProps<P, X>(parentComponentWindow : CrossDomainWindowType, propsDef : PropsDefinitionType<P, X>, props : PropsType<P>, origin : string, helpers : ChildHelpers<P, X>, isUpdate : boolean = false) : ChildPropsType<P, X> {

const result = {};

Expand Down
48 changes: 31 additions & 17 deletions src/component/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
import { setup as setupPostRobot, on, send, bridge, toProxyWindow, destroy as destroyPostRobot } from 'post-robot/src';
import { ZalgoPromise } from 'zalgo-promise/src';
import { isWindow, getDomain, type CrossDomainWindowType } from 'cross-domain-utils/src';
import { noop, isElement, cleanup, memoize, identity, extend } from 'belter/src';
import { noop, isElement, cleanup, memoize, identity, extend, uniqueID } from 'belter/src';

import { getChildPayload, childComponent, type ChildComponent } from '../child';
import { type RenderOptionsType, type ParentHelpers, parentComponent } from '../parent/parent';
import { CONTEXT, POST_MESSAGE, WILDCARD, METHOD } from '../constants';
import { ZOID, CONTEXT, POST_MESSAGE, WILDCARD, METHOD } from '../constants';
import { react, angular, vue, vue3, angular2 } from '../drivers';
import { getGlobal, destroyGlobal } from '../lib';
import type { CssDimensionsType, StringMatcherType } from '../types';

import { validateOptions } from './validate';
import { defaultContainerTemplate, defaultPrerenderTemplate } from './templates';
import { getBuiltInProps, type UserPropsDefinitionType, type PropsDefinitionType, type PropsInputType, type PropsType } from './props';
import { getBuiltInProps, type UserPropsDefinitionType, type PropsDefinitionType, type PropsInputType,
type PropsType, type ParentPropType, type exportPropType } from './props';

type LoggerPayload = { [string] : ?string | ?boolean };

Expand Down Expand Up @@ -227,7 +228,7 @@ export type Component<P, X, C> = {|
driver : (string, mixed) => mixed,
isChild : () => boolean,
canRenderTo : (CrossDomainWindowType) => ZalgoPromise<boolean>,
registerChild : () => ?ChildComponent<P>
registerChild : () => ?ChildComponent<P, X>
|};

export function component<P, X, C>(opts : ComponentOptionsType<P, X, C>) : Component<P, X, C> {
Expand Down Expand Up @@ -255,7 +256,7 @@ export function component<P, X, C>(opts : ComponentOptionsType<P, X, C>) : Compo
return Boolean(payload && payload.tag === tag && payload.childDomain === getDomain());
};

const registerChild = memoize(() : ?ChildComponent<P> => {
const registerChild = memoize(() : ?ChildComponent<P, X> => {
if (isChild()) {
if (window.xprops) {
delete global.components[tag];
Expand All @@ -273,10 +274,10 @@ export function component<P, X, C>(opts : ComponentOptionsType<P, X, C>) : Compo
return true;
});

const delegateListener = on(`${ POST_MESSAGE.DELEGATE }_${ name }`, ({ source, data: { overrides } }) => {
const delegateListener = on(`${ POST_MESSAGE.DELEGATE }_${ name }`, ({ source, data: { uid, overrides } }) => {
return {
parent: parentComponent({
options, overrides, parentWin: source
uid, options, overrides, parentWin: source
})
};
});
Expand Down Expand Up @@ -335,6 +336,8 @@ export function component<P, X, C>(opts : ComponentOptionsType<P, X, C>) : Compo
const init = (inputProps? : PropsInputType<P> | void) : ZoidComponentInstance<P, X, C> => {
// eslint-disable-next-line prefer-const
let instance;

const uid = `${ ZOID }-${ tag }-${ uniqueID() }`;
const props = inputProps || getDefaultInputProps();

const { eligible: eligibility, reason } = eligible({ props });
Expand All @@ -352,7 +355,7 @@ export function component<P, X, C>(opts : ComponentOptionsType<P, X, C>) : Compo
};

const parent = parentComponent({
options
uid, options
});

parent.init();
Expand All @@ -375,18 +378,29 @@ export function component<P, X, C>(opts : ComponentOptionsType<P, X, C>) : Compo

const getChildren = () : C => {
// $FlowFixMe
const childComponents : {| [string] : ZoidComponent<*> |} = children();
const childComponents : {| [string] : ZoidComponent<mixed> |} = children();
const result = {};

for (const childName of Object.keys(childComponents)) {
const Child = childComponents[childName];
result[childName] = (childInputProps) => Child({
...childInputProps,
parent: {
props: parent.getProps(),
export: parent.export
}
});
const Child : ZoidComponent<mixed> = childComponents[childName];

result[childName] = (childInputProps) => {
const parentProps : PropsType<P> = parent.getProps();
const parentExport : exportPropType<X> = parent.export;

const childParent : ParentPropType<P, X> = {
uid,
props: parentProps,
export: parentExport
};

const childProps : PropsInputType<mixed> = {
...childInputProps,
parent: childParent
};

return Child(childProps);
};
}

// $FlowFixMe
Expand Down
Loading

0 comments on commit 4c0ae56

Please sign in to comment.