@acpaas-ui/embeddable-widgets 中文文档教程
ACPaaS UI Embeddable Widgets
该库支持在运行时使用 iframe 将一个应用程序的一部分跨框架嵌入到另一个应用程序的页面中。
可以嵌入的应用程序部分称为小部件。 发布小部件的应用称为发布者。 将发布的小部件嵌入到页面中的应用程序称为容器。
小部件可以声明在容器页面中可用的 API,即使容器和发布者是在不同的前端框架中实现的。
Using
Including
发布和嵌入的第一步是将小部件库包含到页面中。
<script src="https://cdn.antwerpen.be/aui_embeddable_widgets/2.0.0/aui-embeddable-widgets.min.js"></script>
如果你不想从 CDN 加载,你也可以 npm install @acpaas-ui/embeddable-widgets
你会在 node_modules/@acpaas-ui/embeddable 中找到库-widgets/lib
文件夹。
Publishing
您可以将任何网页发布为可嵌入的小部件。
Declaring
首先,您需要在 Web 可访问的 URL 上以 JSON 格式发布小部件的定义。
{
"tag": "my-foo-widget",
"url": "/foo-widget",
"dimensions": {
"width": "100%",
"height": "500px"
},
"props": {
"fooData": {
"type": "array",
"required": true
},
"onFoo": {
"type": "function",
"required": false
}
}
}
这是怎么回事:
- The
tag
is a unique identifier for the widget in the page (it is not automatically mapped to a HTML tag). - The
url
points to where the widget's page is hosted. It can be relative (to the JSON's URL) or absolute.
不需要将定义托管在与小部件相同的服务器上。
- The
dimensions
specify the initial rendering dimensions, applies as style attributes to the iframe. - The
props
specify the properties that the widget can be initialized with fooData
is an array which will be passed from container to widgetonFoo
is an event handler which will be defined in the container and called by the widget
如果你想在同一个页面上多次渲染同一个组件,但具有不同的定义值。 您将必须 define()
具有不同标签的组件。
有关详细信息,请参阅下面的 API 部分。
需要在此 JSON 上设置 CORS 标头(例如
Access-Control-Allow-Origin: *
)。 不需要在小部件页面本身上设置 CORS 标头。
Angular
要发布 Angular 6+ 应用程序,请使用 Angular 包装器 ngx-embeddable-widgets。 它包括一个示例应用程序。
Other Frameworks
初始化小部件:
window.auiEmbeddableWidgets.load('//example.com/path/to/definition.json');
访问从 window.xprops
中的容器传递的属性。
function doFoo() {
if (window.xprops && window.xprops.fooData) {
const result = window.xprops.fooData.map(...);
if (window.xprops.onFoo) {
window.xprops.onFoo(result);
}
}
}
小部件的页面不需要跨源标头。 它通过
window.postMessage
与容器通信。 但是,如有必要,它应该允许通过设置适当的X-Frame-Options
或Content-Security-Policy
标头来构建框架。
Embedding
Angular
要嵌入 Angular 6+ 应用程序,请使用 Angular 包装器 ngx-embeddable-widgets。
React
import React from 'react';
import ReactDOM from 'react-dom';
const MyWidget = window.auiEmbeddableWidgets.reactComponent(
// url to the definition
"//example.com/path/to/defintion.json",
{ React, ReactDOM }
)
class App extends Component {
onFoo(result) { ... }
render() {
return (
<MyWidget
fooData={ ['one', 'two'] }
onFoo={ result => this.onFoo(result) }
className="my-widget"
/>
);
}
}
这将呈现一个
并应用了(可选)
className
。
Other
Provide a div to render the widget in:
<div id="my-container"></div>
将小部件渲染到 div 中,将必要的属性传递给它:
window.auiEmbeddableWidgets.renderUrl(
'//example.com/path/to/definition.json',
{ fooData: ['one', 'two'],
onFoo: function(result) { ... } },
document.getElementById('my-container')
);
Migrating
如果您当前在应用程序或小部件中使用此库的 v1.x,则必须注意正确升级。 应用程序和小部件必须运行相同的可嵌入小部件库的主要版本。
此库将 auiapi_version 查询参数附加到它加载到 iframe 中的 URL。 基于此,应在小部件页面内加载适当的库版本。 v1.x
用于 _aui_api_version=1
,v2.x
用于 _aui_api_version=2
。
要在小部件的应用程序中加载此库的多个版本,您可以使用 npm 功能来拥有同一库的多个版本。
如果 widget 和 app 分开托管,建议的升级
- Upgrade the widget to interpret
_aui_api_version
and load the appropriate library version. - Upgrade the app to use the new major version of the library.
API
window.auiEmbeddableWidgets
:
策略 覆盖并返回一个组合对象,其中包含实例化组件所需的一切。 对象具有:
选项:具有处理过的默认值的定义
覆盖:您传递下来的覆盖
组件:将属性传递给并实例化的函数
每个小部件都有一个唯一的标签。 每个标签在页面中只能定义一次,但可以渲染多次。 但是,如果您想更改同一个小部件的尺寸,则必须使用新标签重新定义一个。
isDefined(tag: string): boolean
如果小部件已在页面中定义,则返回 true。
load(url: string, ?overrides: object): Promise
从 URL 加载小部件定义,将可选覆盖应用于加载的定义,然后返回小部件的句柄以进行实例化。
render(tag: string|object, props: object, elem: HTMLElement): object
使用指定的 props 参数将先前定义的小部件渲染到指定的元素中,并返回实例的句柄。
tag
可以是从加载操作返回的小部件实例,或其标记字符串。renderUrl(url: string, props: object, elem: HTMLElement, ?overrides: object): Promise
从 URL 加载小部件定义(如果尚未加载),将可选的覆盖应用于加载定义,然后使用给定的 props 参数将其渲染到指定的元素。 返回呈现实例的承诺。
reactComponent(url: string, deps: object, ?overrides: object): object
为小部件创建一个 React 组件,其定义托管在
url
中,并应用可选的覆盖那个定义。 deps 对象必须包含 React 提供的React
和ReactDOM
对象。
Definition attributes
小部件定义的可能属性:
tag string
[required]
组件的标记名称,用于:
- Loading the correct component in the child window or frame
- Generating framework drivers
- Logging
tag: 'my-component-tag'
url string
[required]
呈现小部件时将加载的 URL。 可以相对于 JSON 的 URL 或绝对。
url: 'https://example.com/foo-widget'
url: '/foo-widget'
dimensions { width : string, height : string }
小部件的初始尺寸,以 css 样式为单位,支持 px
或 %
。
dimensions: {
width: '300px',
height: '200px'
}
dimensions: {
width: '80%',
height: '90%'
}
尺寸是在
define()
时设置的,而不是在render()
时设置的。 要从默认值覆盖尺寸,可以将它们作为overrides
参数的属性传递给renderUrl
。 但是,这将在连续调用同一小部件的renderUrl()
时被忽略。 要使用不同的尺寸多次呈现相同的小部件,建议使用宽度和高度 100%,并将每个小部件放在适当大小的容器 div 中。
props Object<string, Object>
渲染时可以传递给小部件的道具(数据或函数)。
props: {
onLogin: {
type: 'function'
},
prefilledEmail: {
type: 'string',
required: false
}
}
Default props
scrollTo(yPos: Numer, tag: String)
有时您需要将页面滚动到 iframe 外部以匹配 iframe 内部的元素。 为此,可以从小部件内部调用 scrollTo 属性:
this.props.scrollTo(elementOffset, this.props.tag);
库提供了 scrollTo
的默认处理程序。 您可以通过将自己的实现作为 props.scrollTo
来覆盖它,例如,以补偿标题元素。 这是默认实现:
const scrollTo = (elementOffset, tag) => {
const containerElement = document.querySelector(`[id^='zoid-${tag}-']`);
const newTopOffset = containerElement.offsetParent.offsetTop + elementOffset;
window.scrollTo({
top: newTopOffset,
behavior: 'smooth',
});
};
注意:
window.scrollTo
由该库填充。
Prop Options
type
string
prop
'string'
'number'
'boolean' 期望的数据类型
'对象'
'函数'
'数组'
必需
布尔值
该道具是否是强制性的。 默认为
true
。
onLogin: {
type: 'function',
required: false
}
defaultValue
prop 的默认值(如果在渲染时未传递)。
required
必须为假。
fooData: {
type: "array",
required: false,
defaultValue: ["one", "two"]
}
这可以是任何类型的值。 但是,如果您传递一个函数,它将以 props
作为第一个参数被调用。 因此,如果您想要一个函数作为 defaultValue
,请确保将其包装起来。
查询参数
boolean | string
是否应该在 url 中传递一个 prop(这样它可以影响路由)?
email: {
type: 'string',
queryParam: true // ?email=foo@bar.com
}
如果设置了字符串,则指定将使用的 url 参数名称。
email: {
type: 'string',
queryParam: 'user-email' // ?user-email=foo@bar.com
}
serialization
string
如果是
json
,prop 将在插入到 url 之前被 JSON stringified
user: {
type: 'object',
serialization: 'json' // ?user={"name":"Zippy","age":34}
}
如果是 dotify
,prop 将转换为点符号。
user: {
type: 'object',
serialization: 'dotify' // ?user.name=Zippy&user.age=34
}
如果是 base64
,prop 将被 JSON 字符串化,然后在插入 url 之前进行 base64 编码
user: {
type: 'object',
serialization: 'base64' // ?user=eyJuYW1lIjoiWmlwcHkiLCJhZ2UiOjM0fQ==
}
autoResize { height: boolean, width: boolean, element: string }
当子窗口小部件窗口大小更改时,使容器的 iframe 自动调整大小。
autoResize: {
width: false,
height: true,
}
请注意,默认情况下它与内容的 body
元素相匹配。 您可以通过将自定义选择器指定为 element
属性来覆盖此设置。
autoResize: {
width: false,
height: true,
element: '.my-selector',
}
建议仅对高度使用 autoResize。 宽度有一些奇怪的效果,尤其是当存在滚动条时。
defaultLogLevel string
小部件内部的默认日志记录级别,有助于调试。 选项是:
'debug'
'info'
'warn'
(default)'error'
defaultLogLevel: 'info'
请注意,可以通过在呈现组件时将
logLevel
作为道具传递来覆盖此值。
Additional properties
查看 Zoid API 文档了解更多属性。 基于函数的属性只能指定为覆盖,不能在 JSON 中指定。
Developing
- Run a server publishing the embeddable widgets framework
npm install
npm start
- Point your publisher and container apps to this locally hosted version.
有关更多详细信息,请参阅贡献指南。
Design notes
该框架利用 Zoid 框架,它实现了使用 iframe 和 postMessage API 将应用程序嵌入其他应用程序的样板。
包装器是必要的,以提供更适合 Digipolis 开发项目需求的不同开发人员体验。
- Zoid loads widget definitions synchronously from a script tag. This framework loads them asynchronously from JSON
- no foreign code needs to execute inside the container app, low-risk
- changes to the JSON's schema are easy to support with framework upgrades
- no globals aside from the widgets framework itself
- Zoid requires the widget to know its own URL's, this doesn't
- The widget does not need to known its own absolute URL, because the JSON can have a relative
url
- The widget framework uses the absolute URL of the JSON passed to
renderUrl
to determine the URL for the widget page itself - Zoid supports popup windows, this doesn't
- It adds a lot of code, and it still is very tricky in IE (see zoid documentation)
- All zoid API's are wrapped to allow replacing zoid later on and to support additional logic
- defaultLogLevel = warn, whereas zoid has defaultLogLevel = info (which is spammy)
- Can still be overridden by the widget's JSON
- The framework itself is purely client-side, to allow hosting on a CDN.
Contributing
始终欢迎拉取请求,但请记住以下事项:
- New features (both breaking and non-breaking) should always be discussed with the repo's owner. If possible, please open an issue first to discuss what you would like to change.
- Fork this repo and issue your fix or new feature via a pull request.
- Please make sure to update tests as appropriate. Also check possible linting errors and update the CHANGELOG if applicable.
Support
Joeri Sebrechts (joeri.sebrechts@digipolis.be)
License
版权所有 (c) 2019 年至今,Digipolis
ACPaaS UI Embeddable Widgets
This library enables cross-framework embedding of a part of one application into a page of another application at run-time using iframes.
An application part that can be embedded is called a widget. The app which publishes the widget is called the publisher. The app which embeds the published widget into a page is called the container.
Widgets can declare API's that are available in the container's page, even if container and publisher are implemented in different front-end frameworks.
Using
If you are migrating from v1.x of this framework to v2.x be aware that there are breaking changes, see the migration notes as well as the changelog.
Including
A first step for both publishing and embedding is including the widgets library into the page.
<script src="https://cdn.antwerpen.be/aui_embeddable_widgets/2.0.0/aui-embeddable-widgets.min.js"></script>
If you don't want to load from CDN, you can also npm install @acpaas-ui/embeddable-widgets
and you will find the library in the node_modules/@acpaas-ui/embeddable-widgets/lib
folder.
Publishing
You can publish any webpage as an embeddable widget.
Declaring
First you need to publish the definition of the widget on a web-accessible URL as JSON.
{
"tag": "my-foo-widget",
"url": "/foo-widget",
"dimensions": {
"width": "100%",
"height": "500px"
},
"props": {
"fooData": {
"type": "array",
"required": true
},
"onFoo": {
"type": "function",
"required": false
}
}
}
What is going on here:
- The
tag
is a unique identifier for the widget in the page (it is not automatically mapped to a HTML tag). - The
url
points to where the widget's page is hosted. It can be relative (to the JSON's URL) or absolute.
It is not required that the definition is hosted on the same server as the widget.
- The
dimensions
specify the initial rendering dimensions, applies as style attributes to the iframe. - The
props
specify the properties that the widget can be initialized with fooData
is an array which will be passed from container to widgetonFoo
is an event handler which will be defined in the container and called by the widget
If you want to render the same component multiple times on the same page but with different definition values. You will have to define()
the component with different tags.
See the API section below for more details.
CORS headers need to be set on this JSON (e.g.
Access-Control-Allow-Origin: *
). CORS headers do not need to be set on the widget page itself.
Angular
To publish an Angular 6+ app, use the Angular wrapper ngx-embeddable-widgets. It includes an example app.
Other Frameworks
Initialize the widget:
window.auiEmbeddableWidgets.load('//example.com/path/to/definition.json');
Access the properties passed from the container in window.xprops
.
function doFoo() {
if (window.xprops && window.xprops.fooData) {
const result = window.xprops.fooData.map(...);
if (window.xprops.onFoo) {
window.xprops.onFoo(result);
}
}
}
The widget's page does not need cross-origin headers. It communicates to the container via
window.postMessage
. However, it should allow framing by setting appropriateX-Frame-Options
orContent-Security-Policy
headers if necessary.
Embedding
Angular
To embed into an Angular 6+ app, use the Angular wrapper ngx-embeddable-widgets.
React
import React from 'react';
import ReactDOM from 'react-dom';
const MyWidget = window.auiEmbeddableWidgets.reactComponent(
// url to the definition
"//example.com/path/to/defintion.json",
{ React, ReactDOM }
)
class App extends Component {
onFoo(result) { ... }
render() {
return (
<MyWidget
fooData={ ['one', 'two'] }
onFoo={ result => this.onFoo(result) }
className="my-widget"
/>
);
}
}
This renders a
<div>
with the (optional)className
applied to it.
Other
Provide a div to render the widget in:
<div id="my-container"></div>
Render the widget into the div, passing it the necessary properties:
window.auiEmbeddableWidgets.renderUrl(
'//example.com/path/to/definition.json',
{ fooData: ['one', 'two'],
onFoo: function(result) { ... } },
document.getElementById('my-container')
);
Migrating
If you're currently using v1.x of this library in an app or in a widget, care must be taken to upgrade properly. Both app and widget must be running the same major version of the embeddable widgets library.
This library appends the auiapi_version query parameter to the URL it loads into the iframe. Based on this the appropriate library version should be loaded inside of the widget's page. v1.x
for _aui_api_version=1
, and v2.x
for _aui_api_version=2
.
To load multiple versions of this library inside the widget's app, you can use the npm feature to have multiple versions of the same library.
A suggested upgrade strategy in case widget and app are separately hosted:
- Upgrade the widget to interpret
_aui_api_version
and load the appropriate library version. - Upgrade the app to use the new major version of the library.
API
window.auiEmbeddableWidgets
define(definition: object, ?overrides: object): object
Defines a widget from the specified definition (same as the JSON described above) and definition overrides and returns a composed object with everything required to instantiate a component. Object has:
options: the definition with processed default values
overrides: the overrides you passed down
component: a function to pass the properties to and instantiate
Each widget has a unique tag. Each tag can only be defined once in the page, but can be rendered multiple times. However if you want to change the dimensions on the same widget, you will have to redefine one with a new tag.
isDefined(tag: string): boolean
Returns true if the widget is already defined in the page.
load(url: string, ?overrides: object): Promise<object>
Loads a widget definition from a URL, applies the optional overrides to the loaded definition, then returns a handle to the widget for instantiating.
render(tag: string|object, props: object, elem: HTMLElement): object
Renders a previously defined widget with the specified props parameters into the specified element and returns a handle to the instance.
tag
can be the widget instance returned from the load operation, or its tag string.renderUrl(url: string, props: object, elem: HTMLElement, ?overrides: object): Promise<object>
Loads a widget definition from URL (if not yet loaded), applies the optional overrides to the loaded definition, then renders it to the specified element with the given props parameters. Returns a promise for the rendered instance.
reactComponent(url: string, deps: object, ?overrides: object): object
Creates a React component for the widget with definition hosted at
url
, with the optional overrides applied to that definition. The deps object must contain theReact
andReactDOM
objects provided by React.
Definition attributes
The possible attributes for the widget definition:
tag string
[required]
A tag-name for the component, used for:
- Loading the correct component in the child window or frame
- Generating framework drivers
- Logging
tag: 'my-component-tag'
url string
[required]
The URL that will be loaded when the widget is rendered. Can be relative to the JSON's URL or absolute.
url: 'https://example.com/foo-widget'
url: '/foo-widget'
dimensions { width : string, height : string }
The initial dimensions for the widget, in css-style units, with support for px
or %
.
dimensions: {
width: '300px',
height: '200px'
}
dimensions: {
width: '80%',
height: '90%'
}
Dimensions are set at
define()
time, not atrender()
time. To override dimensions from the defaults they can be passed as a property of theoverrides
argument torenderUrl
. However, this will be ignored on successive calls torenderUrl()
for the same widget. To render the same widget multiple times with different dimensions it is suggested to use width and height 100% and put each widget in a container div that is appropriately sized.
props Object<string, Object>
Props that can be passed to the widget when rendering (data or functions).
props: {
onLogin: {
type: 'function'
},
prefilledEmail: {
type: 'string',
required: false
}
}
Default props
scrollTo(yPos: Numer, tag: String)
Sometimes you need to scroll the page outside the iframe to match an element inside the iframe. In order to do this, from inside the widget the scrollTo prop can be called:
this.props.scrollTo(elementOffset, this.props.tag);
There is a default handler for scrollTo
that is provided by the library. You can override it by passing your own implementation as props.scrollTo
, for example, to compensate for header elements. This is the default implementation:
const scrollTo = (elementOffset, tag) => {
const containerElement = document.querySelector(`[id^='zoid-${tag}-']`);
const newTopOffset = containerElement.offsetParent.offsetTop + elementOffset;
window.scrollTo({
top: newTopOffset,
behavior: 'smooth',
});
};
NOTE:
window.scrollTo
is polyfilled by this library.
Prop Options
type
string
The data-type expected for the prop
'string'
'number'
'boolean'
'object'
'function'
'array'
required
boolean
Whether or not the prop is mandatory. Defaults to
true
.
onLogin: {
type: 'function',
required: false
}
defaultValue
The default value for the prop if not passed at render time.
required
must be false.
fooData: {
type: "array",
required: false,
defaultValue: ["one", "two"]
}
This can be any type of value. However if you pass a function, it will be called with props
as the first argument. So if you want to have a function as defaultValue
, make sure you wrap it.
queryParam
boolean | string
Should a prop be passed in the url (so it can influence the routing)?
email: {
type: 'string',
queryParam: true // ?email=foo@bar.com
}
If a string is set, this specifies the url param name which will be used.
email: {
type: 'string',
queryParam: 'user-email' // ?user-email=foo@bar.com
}
serialization
string
If
json
, the prop will be JSON stringified before being inserted into the url
user: {
type: 'object',
serialization: 'json' // ?user={"name":"Zippy","age":34}
}
If dotify
the prop will be converted to dot-notation.
user: {
type: 'object',
serialization: 'dotify' // ?user.name=Zippy&user.age=34
}
If base64
, the prop will be JSON stringified then base64 encoded before being inserted into the url
user: {
type: 'object',
serialization: 'base64' // ?user=eyJuYW1lIjoiWmlwcHkiLCJhZ2UiOjM0fQ==
}
autoResize { height: boolean, width: boolean, element: string }
Makes the container's iframe resize automatically when the child widget window size changes.
autoResize: {
width: false,
height: true,
}
Note that by default it matches the body
element of your content. You can override this setting by specifying a custom selector as an element
property.
autoResize: {
width: false,
height: true,
element: '.my-selector',
}
Recommended to only use autoResize for height. Width has some strange effects, especially when scroll bars are present.
defaultLogLevel string
The default logging level for the widget's internals, helpful for debugging. Options are:
'debug'
'info'
'warn'
(default)'error'
defaultLogLevel: 'info'
Note that this value can be overriden by passing
logLevel
as a prop when rendering the component.
Additional properties
Check the Zoid API documentation for additional properties. The function-based properties can only be specified as overrides, not in the JSON.
Developing
- Run a server publishing the embeddable widgets framework
npm install
npm start
- Point your publisher and container apps to this locally hosted version.
See the contribution guide for additional details.
Design notes
This framework makes use of the Zoid framework, which implements the boilerplate for embedding apps into other apps using iframes and the postMessage API.
The wrapper is necessary to allow for a different developer experience which is more suited to the needs of Digipolis development projects.
- Zoid loads widget definitions synchronously from a script tag. This framework loads them asynchronously from JSON
- no foreign code needs to execute inside the container app, low-risk
- changes to the JSON's schema are easy to support with framework upgrades
- no globals aside from the widgets framework itself
- Zoid requires the widget to know its own URL's, this doesn't
- The widget does not need to known its own absolute URL, because the JSON can have a relative
url
- The widget framework uses the absolute URL of the JSON passed to
renderUrl
to determine the URL for the widget page itself - Zoid supports popup windows, this doesn't
- It adds a lot of code, and it still is very tricky in IE (see zoid documentation)
- All zoid API's are wrapped to allow replacing zoid later on and to support additional logic
- defaultLogLevel = warn, whereas zoid has defaultLogLevel = info (which is spammy)
- Can still be overridden by the widget's JSON
- The framework itself is purely client-side, to allow hosting on a CDN.
Contributing
Pull requests are always welcome, however keep the following things in mind:
- New features (both breaking and non-breaking) should always be discussed with the repo's owner. If possible, please open an issue first to discuss what you would like to change.
- Fork this repo and issue your fix or new feature via a pull request.
- Please make sure to update tests as appropriate. Also check possible linting errors and update the CHANGELOG if applicable.
Support
Joeri Sebrechts (joeri.sebrechts@digipolis.be)
License
Copyright (c) 2019-present, Digipolis