最近遇到一个问题,页面上嵌入的一个表单需要调用页面上的一个函数,因为表单是由表单系统提供的,所以他们不在一个域名下。
稍微查找了一下,这种一般是用 iframe 大法,因为虽然说是跨域,但其实浏览器的内部实现里还是有部分属性可以调用到的,其中window.parent , window.top,window.frames 之类 ,而如果这些window对象的 domain 如果是相同的,你就可以调用其中的方法了,所以根据这个思想,我写了一个比较通用的页面来实现。
不过可能有些童鞋不清楚这个 iframe大法具体怎么用,这里简要介绍一下。
假设一个场景,你的页面是d1/main.htm, 上面嵌入了一个第三方地图,比如就叫 d2/map.htm吧,main 上面有一个输入框,你输入了一个地方,map就会自动跳转到那个位置,这里使用的是 d2/map.htm 上的 move('place'); 这个js方法。
如果你试图通过获取 iframeMap.contentWindow.move(''),浏览器会告诉你,跨域了,无法调用。 这时候怎么办呢,首先,让d2提供一个 d2/func.htm, 内容如下
<script>
window.parent.move(param(place)); // 这里的 window.parent 是有问题的,不要吐槽了,举个例子而已 ^_^
</script>
然后在 main.htm 上弄一个隐藏的 iframeFunc ,然后 function move() { iframeFunc.src = 'd2/func.htm?place=place'; }, 这样,iframeFunc 载入的时候就会调用d2/map.htm 上的 move 方法了,iframe大法完成。
所以,这个通用页面只是处理一下这里的 window.parent 这个问题。
首先,func.htm 里面的 window.parent 是跨域的父页面。 而实际要调用的函数就不知道在哪个页面上了,所以虽然说是通用,其实还是无法考虑到全面,这里只查找window.parent.parent... 和 window.parent.frames... 这些.
上源码:
<script type="text/javascript"> // 参数说明 // page 要调用函数的页面地址 a.html // func 要调用的函数名称 // params 经过编码的字符串,调用函数的参数,多参数请自行修改 var url = window.location.href; var paraString = url.substring(url.indexOf("?")+1,url.length).split("&"); var paraObj = {}; for (i=0; j=paraString[i]; i++){ paraObj[j.substring(0,j.indexOf("=")).toLowerCase()] = j.substring(j.indexOf("=")+1,j.length); } if (paraObj['func'] && paraObj['page']) { function func_run(win, flag, url, params, defRun) { try { var hasRun = false; if (flag) { hasRun = win.func_flag && flag == win.func_flag; } if (url && !hasRun) { hasRun = win.location.pathname.indexOf(url) > -1; } if (hasRun) { var jsStr = 'parent.' + paraObj['func'] + '("' + params + '");'; eval(jsStr); } return hasRun; } catch (e) { return false; } } var parentTemp = window.parent; var targetPage = paraObj['page']; var hasRun = false; var params = paraObj['params']; var last = null; while (parentTemp) { if (hasRun) break; if (last != parentTemp) last = parentTemp; else break; try { hasRun = parentTemp.location.pathname.indexOf(targetPage) > -1; if (hasRun) { var js = 'parentTemp.' + paraObj['func'] + ((params) ? '(' + params + ');' : '();'); eval(js); break; } } catch (e) { } if (!hasRun) { for (var i = 0; i < parentTemp.frames.length; ++i) { try { var temp = parentTemp.frames[i].contentWindow; hasRun = temp.location.pathname.indexOf(targetPage) > -1; if (hasRun) { var js = 'parentTemp.' + paraObj['func'] + ((params) ? '(' + params + ');' : '();'); eval(js); break; } } catch (e) { } } } parentTemp = parentTemp.parent; } } </script>
其实本来是可以进行进一步的扩展的,首先是添加一个参数, flag ,而不是用页面的地址,在页面上预定义一个变量,如
window.func_flag = ‘1’; 而调用 c.html 的时候加上 func_flag=1, 这样只会对func_flag=1的页面进行调用。
另外params多参数。
还有run=once/all 这样,对 flag 和 page 之类的是只调用一次,还是对所有的页面调用,想了一下午,脑袋都炸了,不写那么多了。
OVER.
后续:
后来在实验中发现,表单页面不让添加iframe,所以上面的方法就悲剧掉了。所以使用了 postMessage大法。
在调用的时候
var msg = {};
msg.event = 'newPage';
msg.url = 'http://www.cnblogs.com/willin';
var msgJson = '({"event":"'+msg.event+'","url":,"'+msg.url+'"})'
parent.parent.postMessage(msg, '*'); // 这个后面的 ‘*’ 是指传入的页面地址。如 cnblogs.com,这样,如果parent.parent 不是 cnblogs.com 就不会触发。
在cnblogs.com 这个页面上加入
if (window.addEventListener)
window.addEventListener('message', function(event){
var data = eval(event.data);
if (data.event == 'newPage') {
window.addTab(data.url);
}
}, false);
在现代浏览器上都是可行的,据说IE7及一下不行,唉,还好XP下还是能用IE8的。 真希望IE6和7早点去死掉啊。