之前基於Jquery mobile做了一個Chrome app,就在要給客戶做showcase的時候,chrome強制升級manifest_version到2(http://developer.chrome.com/extensions/manifestVersion.html),而這個version基於安全考慮做了一些限制,導致我的chrome app無法運行,具體限制可見:http://developer.chrome.com/extensions/contentSecurityPolicy.html。對於我的app主要有以下兩個影響點:
- 限制dom元素上直接onclick
- 限制new Function
這兩點對於我來說比較棘手。
1)去onclick
由於之前寫js不規範,爲了傳參數方便,也由於存在動態創建dom的需求,所以就直接onclick,整個app有多達十幾個onclick,比如:
var item = "<li data-theme=\"c\"><a data-transition=\"slide\" οnclick=\"loadViewList('"+data.data[i]+"')\">"+data.data[i]+"</a></li>";
$(item).insertAfter($("#projectListTitle"));
這裏給我出的難題是這些dom是在某種場景下創建出來的dom,很難在統一的做事件偵聽,再比如:
<li><a id='loadAssignToButton' οnclick='loadAssignTo(${id})'>Assign CR</a></li>
這裏的難題是這段代碼是模版裏的,參數是在模版渲染時才能確定的,很難在js事件偵聽裏傳參。並且以上問題在原有代碼裏存在很多,需要有個一勞永逸的方法來解決。幸好,jquery很強大,一方面它提供了很好的selector,能讓我很方便的一處監聽所有需要處理的dom,另一方面,它有一個delegate的機制,也就是說除了能直接在dom上進行監聽外,還能讓這個dom監視它的子dom,能實現當子dom創建時再監聽事件的功能,具體可見jquery.on api(http://api.jquery.com/on/)。於是,我給出了一個一勞永逸的方法:
$(document).ready(function() {
$("div[data-role='page']").on("click", "a", function(event) {
if($(this).attr("cf")){
var funName = $(this).attr("cf");
if(funName.indexOf(".") == -1){
window[funName]($(this).attr('cp1'),$(this).attr('cp2'));
}else{
var funNames = funName.split(".");
window[funNames[0]][funNames[1]]($(this).attr('cp1'),$(this).attr('cp2'));
}
}
});
});
然後,改寫所有用到onclick的點,比如:
<li><a id='loadAssignToButton' cf='loadAssignTo' cp1='${id}'>Assign CR</a></li>
其中,cf,cp1和cp2分別對應着onclick監聽處理函數名和參數名,是自定義屬性,只是爲了傳參,當然也有更優雅的方式,不過這不是這裏的重點。
2)去new Function
如果只是簡單的使用new Function還好辦,不過我這裏不是直接使用了new Function,而是引用的jquery template裏面用到了new Function,並且這是做模版的核心(通過動態function來渲染模版),具體用到的地方如下所示:
// Generate a reusable function that will serve to render a template against data
function buildTmplFn( markup ) {
return new Function("jQuery","$item",
......
)}
解決這個問題要麼是換模版方案,要麼就是使用chrome提供的sandbox特性,由於應用模版的地方較多,換模版的成本太大,所以只好看看這個sandbox能否解決我的問題。使用方法可見:http://developer.chrome.com/trunk/apps/manifest.html#sandbox於是乎,我照葫蘆畫瓢通過sandbox來改寫使用了模版的地方。
1. 首先新建sandbox.html,把模版挪過來
2. 在sandbox.html,添加模版渲染的請求偵聽:
<script>
// Set up message event handler:
window.addEventListener('message', function(event) {
var data = event.data.data;
var tmpl = event.data.tmpl;
var dataRendered = $( "#"+tmpl ).tmpl( data ).wrap('<div>').parent().html();
var result = {'data':data,'dataRendered':dataRendered,'tmpl':tmpl};
event.source.postMessage({'result': result}, event.origin);
});
</script>
其中$( "#"+tmpl ).tmpl( data ).wrap('<div>').parent().html()是由於$( "#"+tmpl ).tmpl( data )返回的是jquery的dom,sandbox是基於HTML5 web worker來做的,它並不是同享內存,所以不能傳遞dom,這裏只好先把dom轉爲html,然後在主頁面裏通過html創建dom。
3. 在主頁面裏添加隱藏的iframe來向sandbox發請求
<iframe id="theFrame" src="sandbox.html" style="display: none;"></iframe>
var message = {'data':data,'tmpl':'crFilterTemplate'};
document.getElementById('theFrame').contentWindow.postMessage(message, '*');
4. 在主頁面監聽message,取得模版渲染後html,並創建domwindow.addEventListener('message', function(event) {
var data = event.data.result.data;
var dataRendered = event.data.result.dataRendered;
var tmpl = event.data.result.tmpl;
window[tmpl+'Render'](data, dataRendered);
});
function crDetailTemplateRender(data, dataRendered) {
$(dataRendered).appendTo(document.body);
$("#crDetailPage").attr( "data-" + $.mobile.ns + "external-page", true ).one( 'pagecreate', $.mobile._bindPageRemove );
$.mobile.changePage( "#crDetailPage", { transition: "flip"} );
}
升級manifest_version本來沒在工作計劃之內,是突然冒出的攔路虎,不過也給自己提了個醒,一旦基於某個平臺上做事情,一定得多關注這個平臺的遊戲規則,不然陷阱重重啊