# iframe-sdk

[![mui version][mui-version-image]][mui-url]
[![owner][owner-image]][mui-url]

[owner-image]: https://img.shields.io/badge/owner-桐谷-blue.svg
[mui-version-image]: https://img.shields.io/badge/mui-4.2.26-blue.svg
[mui-url]: http://mui.tmall.net/detail/mui/iframe-sdk

基于 postMesssage 和 MutationObserver 封装的 iframe 父子页面通信和高度同步框架。核心功能是让iframe页面表现的像普通页面一样。主要功能：

- iframe页面和iframe标签高度同步，页面动态增高时，不会出现滚动条。
- 拦截页面上的链接跳转，交给父页面执行跳转。避免在iframe中出现“带页头的页面”。
- 封装父子页面的 postMessage 通信接口，对外暴漏更易用的api。

[demo](http://mui.tmall.net/detail/mui/iframe-sdk/demo/zebra/pc)，[ATA介绍](http://www.atatech.org/articles/60742)

已接入iframe-sdk的项目

- 国际供应链平台对接菜鸟
- 小电数码供应链平台对接菜鸟
- 供销对接零售通
- 供销对接新零售

框架分为两部分，parent 页面和 iframe 页面分别使用两个脚本。

<br /><br />
## iframe页面的用法


### 如何引入?

script标签的方式引入，全局就有一个`iframeSDK`听任差遣：

``` html
<script src="//g.alicdn.com/mui/iframe-sdk/4.2.26/iframe.umd.js"></script>
```



在mui组件中使用：

``` js
let iframeSDK = require('mui/iframe-sdk/index').iframe;
// iframeSDK.方法名() 这样使用
```
**注意：如果父页面没引入iframeContainerSDK或当前页不在iframe中，这个脚本不会做任何事情**。


### 接口

如下接口针对父页面未实现 iframeContainerSDK 的情况做了默认处理，不用担心页面不在iframe中时是出现bug。
当父页面实现iframeContainerSDK了，下面接口只是给父页面发送一个消息。具体如何实现父页面可以根据需求酌情处理。

|接口|参数|功能|无父页面或父页面未实现iframeContainerSDK时的处理|
|----|----|----|----|
| emit(type, data)| type:[String] data:[any] | 发送type消息到父容器 | - |
| on(type, cb)| type:[String] cb:[Function]| 监听父容器发送过来的type消息| - |
| setTitle(title)| title:[String] | 通知容器更新更新当前页面的标题 | document.title = title
| open(url, target)| url:[String] target:[enum(_blank, _self, _top, _parent)]| 通知容器打开一个打开url, target与html a标签的target一致 | location.href = url
| reload() | -| 通知容器刷新当前页面 | location.reload() 
| close() | -| 通知父容器关闭自己 | window.close()
| reloadOpener() | -| 通知父容器刷新容器自己 | window.opener.location.reload()|
| login() | -| 通知父容器执行登录操作 | 跳转到淘宝登录页面 |
| config(conf)| - | 设置配置项，参数见 "配置" 一节| -



### 配置

通常不需要任何配置，如果默认配置不满足你的需要可以调用 ```iframeSDK.config(conf)``` 更新配置。**请在调用任何其他方法前，调用config()方法**。

iframeSDK配置示例：

```js
iframeSDK.config({
  ...  
})
```

配置字段和默认值

|字段|功能|默认值|
|----|----|----|
| openInContainerHook | 写在A标签class上用于控制当前A标签必须在父容器中打开的HOOK| J_openInContainer|
| notOpenInContainerHook | 写在A标签class上用于控制当前A标签必须不能在父容器中打开的HOOK| J_notOpenInContainer|
| disableTargetBlankHook | 写在A标签class上用于控制当前A标签target="_blank"无效的HOOK|J_disableTargetBlank|
| linkOpenInContainer | 声明默认所有A标签是否可以在父容器中打开 |true|
| openInContainerFilter | 函数，输入url，判定url是否可以在父容器内打开。如果url是登录连接到url，则判断redirectURL是否可以在父容器打开 |()=>true|
| rewriteWindowOpen | 是否覆盖window.open 为 iframeSDK.open | false | 



### 页面打开行为的控制

打开行为包括两方面：

1. 打开的页面是否需要在父容器中渲染。如果一个页面没有页头，则无论是在当前页面打开，还是新窗口打开，都应该在容器中渲染，避免出现无页头的情况。如果一个页面已经包含了页头、页尾，则无论怎么打开，都不应该在容器中渲染。
2. 打开的位置，新窗口还是当前窗口。

**控制哪些页面可以在父容器中打开**

``` html
<script>
iframeSDK.config({
    //默认所有A标签应该在父容器中打开
    linkOpenInContainer: true,
    //但 域名必须是 xxx.tmall.com
    openInContainerFilter: function(url) { return /^\/\/xxx.tmall.com/.test(url) }
    //控制单个A标签不要在父容器中打开的HOOK
    notOpenInContainerHook : "J_notOpenInContainer",
    //控制单个A标签需要在父容器中打开的HOOK
    openInContainerHook : "J_openInContainer",
    //控制是否覆盖window.open方法为 iframeSDK.open
    rewriteWindowOpen: false
})
</script>

<a href="//xxx.tmall.com">在父容器中打开</a>
<a href="//xxx.tmall.com/download" class="J_notOpenInContainer">下载链接，不在父容器中打开</a>
<a href="//xxx.tmall.com/download" class="J_openInContainer">在父容器中打开</a>
```

写在A标签上的HOOK的优先级高于config配置中的linkOpenInContainer配置

过滤器是最后的校验，A 标签的点击和iframeSDK.open() 方法都会使用过滤器校验url是否可以在父容器中打开。


**控制打开位置**

默认保持了 A 标签的 target 的行为，target="`_blank`" 会在新的浏览器窗口打开，`target` 等于空或者 "`_self`", "`_parent`", "`_top`" 都会交给父容器酌情处理。

iframeSDK.open(url, target) 方法的第二个参数规则与 A 标签的 `target` 属性一致。

如果想禁用 target="_blank" 的默认行为，即禁止在新窗口打开，必须交给父容器打开，可以为A标签添加 `J_disableTargetBlank` 类名，名字可以在 iframeSDK.config() 时设置

```
<a href="xx" target="_blank" class="J_disableTargetBlank" >在父容器中时，不会再新窗口中打开</a>
```

**注意：**

- **仅当父页面存在，且父页面实现了 iframeContainerSDK 时，iframeSDK 才会拦截 A 链接的点击**
- **仅当A标签的href是合法的链接地址，且事件没有阻止默认行为，且事件冒泡到document上，iframeSDK才会处理**



<br /><br />

## parent页面的用法

### 引入

不使用模块加载器的用法:

``` html
<script src="//g.alicdn.com/mui/iframe-sdk/4.2.26/iframeContainerSDK.umd.js"></script>
<script>
var iframeContainer = new IframeContainerSDK(conf);
</script>
```

mui的用法:

``` js
var IframeContainerSDK = require('mui/iframe-sdk/index').Container;
var iframeContainer = new IframeContainerSDK(conf);
```


### 初始化

IframeContainerSDK 构造器方法接受一个参数，结构如下：

|字段|类型|描述|默认值|
|----|---|----|----|
|debug|Boolean|是否调试模式|false|
|onHandShake|Function|子页面与父页面握手（首次通信）时的回调，无参函数|
|originFilter|Function|安全源过滤器，支持正则和函数|见下面|
|iframes| String, Array(dom), dom | 需要初始化的iframe | 默认空，详见下面 |
|iframeConfig | Object | 初始化iframe时使用的参数 | 见下面 |
|containerUrl|String|容器页面的url|
|iframeUrlParam|JSON String|TODO|


**originFilter： 消息来源的安全过滤**

只有通过originFilter过滤的来源的消息才会被处理，默认的过滤器如下

``` js
(source) => 
/^http(s)?:\/\/[w.]*(taobao.com|taobao.net|tmall.com|tmall.net|tmall.hk|tmall.com.hk|95095.com|alitrip.com|xiami.com|1688.com)$/.test(source)
```

**iframes： 需要初始化的iframe**

选择需要被初始化的iframes，可以接收类型：

0. `undefined` 没提供iframes参数，表示将当前页面已有iframe全部初始化
1. `string` 合法的选择器，使用document.querySelectorAll查找
2. `Array` DOM数组
3. `DOM` 单个 iframe DOM 对象

**iframeConfig： 初始化iframe时使用的参数**

|字段|类型|描述|默认值|
|----|---|----|----|
|iframeResizer|Object|内部依赖了iframeResizer，通过iframeResizer设置其配置项，[详见这里](https://github.com/davidjbradshaw/iframe-resizer)


**动态新增iframe的情况**

动态新增的iframe默认不会被 iframeContainerSDK处理，需要调用 `iframeContainerSDK` 实例对象的 `initIframes(iframes, iframeConfig)` 方法进行初始化。参数与构造器的 iframes 、iframeConfig 参数相同

**注意：iframeContainerSDK 在初始化iframe的时候会为没有设置name属性的iframe设置一个全局唯一的name，如果父页面需要自己设置iframe的name，也请保证name属性的全局唯一**

### 接口

iframeContainer 实例上有如下方法：

- on(msgType, callback) 监听事件
- emit(msgType, data, target) 向target发送消息
- off(msgType, callback) 取消监听
- initIframs(iframes, iframeConfig) 详见 初始化 -> 动态新增iframe的情况

msgType 为消息类型，例如iframe页面调用 `iframeSDK.close()`，
父页面需要调用`iframeContainerSDK.on('close'， cb) 监听此消息

callback 第一个参数是消息数据，默认所有消息都会包含如下数据：

``` js
{
  sourceUrl : 'http://xxx', //消息来源页的url,
  iframeName : 'xxx'        //消息来源页的window.name，也就是是父页面中对应的iframe标签的name属性
}
```


callback 第二个参数是消息来源的window对象，方便回传postMessage。一般用不到。不要使用其他属性和方法，可能会跨域的

<br /><br />
### 浏览器兼容性

IE9+ 和 其他


