ZK 版本:6.5.3
故事是這樣開始的:畫面上有一個設定 checkmark=true
的 Listbox。每個 item 除了自動生出來的 checkbox 之外就是 Image,src 是用 data URI(所以基本上沒有 loading 時間的問題),也有設定 height 讓 Listbox 不會長得太可怕。也因此,使用者希望點圖片可以看到夠大的圖、但是又不希望出現 scroll bar。
所以當使用者點圖片的時候,就用 Executions.createComponents()
製造出一個 Window,然後設法讓圖片等比例縮小到長或寬撐滿整個 Window。Window 的程式碼長這樣(VM 就不附了):
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('ScaleImageViewModel')"
closable="true" title="Image" width="90%" height="90%">
<vlayout vflex="1" hflex="1">
<div id="cave" vflex="1" hflex="1" style="overflow:auto;">
<image id="image" src="@load(vm.image)" />
</div>
<hlayout hflex="1">
<button label="Fit Window" w:onClick="resetRatio()" />
<slider id="scale" hflex="1" w:onScroll="changeRatio()"/>
<button label="Pre." onClick="@command('prev')" disabled="@load(vm.start)"/>
<button label="Next" onClick="@command('next')" disabled="@load(vm.end)"/>
</hlayout>
</vlayout>
<script defer="true"><![CDATA[
zk.Widget.$("$cave").onAfterSize = function() {
this.$super("onAfterSize");
resize();
}
scaleWgt = zk.Widget.$("$scale");
caveElmt = $("$cave")[0];
imageElmt = $("$image")[0];
imageOriginWidth = imageElmt.width;
imageOriginHeight = imageElmt.height;
resetRatio();
]]></script>
</window>
<script><![CDATA[
var scaleWgt;
var caveElmt;
var imageElmt;
var imageOriginWidth;
var imageOriginHeight;
var ratio;
function resetRatio() {
var wRatio = caveElmt.offsetWidth / imageOriginWidth;
var hRatio = caveElmt.offsetHeight / imageOriginHeight;
setRatio(
Math.min(
(wRatio > 1) ? 1 : wRatio, //不能比 100% 還大,所以最多壓成 1
(hRatio > 1) ? 1 : hRatio
)
);
scaleWgt.setCurpos(ratio * 100);
}
function resize() {
imageElmt.width = imageOriginWidth * ratio;
imageElmt.height = imageOriginHeight * ratio;
console.log("[after] " + imageElmt.width + "x" + imageElmt.height);
}
function setRatio(value) {
ratio = Math.min(1, value); //不能比 100% 還大,所以最多壓成 1
resize();
}
//其實應該要可以直接 call setRatio(),只是 ZK 我實在...
function changeRatio() {
setRatio(scaleWgt.getCurpos() / 100);
}
]]></script>
我想盡量讓事情都只停在 client side 就處理完成,是說我也不確定調整圖片大小這件事情在 server side 能不能順利解。總之,程式碼可能不夠精煉,而且有些莫名其妙地方是靠 trial and error 得到的可行解、完全不明所以…… 只能說我對 ZK 完全沒有愛,能在零零落落毫無章法的 ZK 文間當中湊出這些,我都覺得減壽三天了…… Zzz
好的,前情提要終於講完了,要開始進入正題了。用實際資料測試的時候發現,有些圖片就是無法改變大小(slider 也沒反應)。原本以為是圖片的問題,但是拿 data URI 的值直接塞 browser 能得到正確的 size……
在經過一番折騰之後,終於發現規律:
Listbox 有一個(以上)item 勾選,就會無法改變大小
既然確定是 ZK 搞出來的問題,那要懷疑的東西就少了一點。又一輪的 trial and error 後,終於發現:Image 的 id 不能是 “image”,不然 $("$iamge")
不知道會 select 到哪個 element 去。
是的,只要換個 id,一切統統都沒事了(為了保險起見,我試過 fullImage
、scaleImage
,不保證其他值不會有問題 Zzz)。
WTF?這到底什麼鬼東西?為什麼 Listbox 有沒有 item 被勾選會影響到 $("$image")
的結果?那個 node 還是在 Window 裡頭(不是說 Window 有自己的 id space?)?而為什麼 ZUL 設定的 id 值對應到 JS / DOM 當中居然不是 unique 的?以後開發人員要自己讓 id 值 unique 嗎(不然有 bug 連要從哪裡開始懷疑起都不知道?)
雖然平常就有在 blame ZK,但是沒遇到過這麼令人傻眼的,這件事情足足讓我笑了十分鐘… lol
教練我想寫 GWT…… [淚目]
後來要修正其他功能的 bug,所以重寫了一次,關鍵部份還是一樣,只是程式碼變得清爽一點,茲更新如下:
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('ScaleImageViewModel')"
closable="true" title="Image" width="90%" height="90%">
<vlayout vflex="1" hflex="1">
<div id="cave" vflex="1" hflex="1" style="overflow:auto;">
<image id="scaleImage" src="@load(vm.image)">
<attribute w:name="setSrc">
function (src) {
this.$setSrc(src);
resetRatio();
}
</attribute>
</image>
</div>
<hlayout hflex="1">
<button label="Fit Window" w:onClick="resetRatio()" />
<slider id="scale" hflex="1" w:onScroll="changeRatio()"/>
<button label="Pre." onClick="@command('prev')" disabled="@load(vm.start)"/>
<button label="Next" onClick="@command('next')" disabled="@load(vm.end)"/>
</hlayout>
</vlayout>
<script defer="true">
//第一次 Image.setSrc() 不會真正 resetRatio(),所以得在 defer 的 script 指定來一次
resetRatio();
</script>
</window>
<script><![CDATA[
function resetRatio() {
var imageElmt = $("$scaleImage")[0];
if (!imageElmt) { return; }
var tempImage = new Image();
tempImage.src = imageElmt.src;
var caveElmt = $("$cave")[0];
var wRatio = caveElmt.offsetWidth / tempImage.width;
var hRatio = caveElmt.offsetHeight / tempImage.height;
setRatio(
Math.min(
(wRatio > 1) ? 1 : wRatio, //不能比 100% 還大,所以最多壓成 1
(hRatio > 1) ? 1 : hRatio
)
);
zk.Widget.$("$scale").setCurpos(ratio * 100);
}
function resize() {
var imageElmt = $("$scaleImage")[0];
var tempImage = new Image();
tempImage.src = imageElmt.src;
imageElmt.width = tempImage.width * ratio;
imageElmt.height = tempImage.height * ratio;
}
/** value 的值域理論上是 [0,1] **/
function setRatio(value) {
ratio = Math.min(1, value); //不能比 100% 還大,所以最多壓成 1
resize();
}
//其實應該要可以直接 call setRatio(),只是 ZK 我實在無法... Orz
function changeRatio() {
setRatio(zk.Widget.$("$scale").getCurpos() / 100);
}
]]></script>