XPages 附件多選批次刪除:官方只能一次刪一個,自己補上「勾選多個、存檔一次刪」
一份 XPages 文件上掛了好幾個附件,你想砍掉其中幾個。這在傳統 Notes 用戶端點一點就好,但在 XPages web 上,官方的 File Download 控制項即使開了 allowDelete="true",實際跑起來也是每列一個刪除連結、一次刪一個,沒有「勾選多個、一次刪」的原生做法。翻過 OpenNTF 和社群,多檔上傳的控制項一堆、附件唯讀清單也有,但「勾選多個附件一次刪」幾乎找不到現成的;連我手上一張做得相當完整的生產 XPages 表單(自訂上傳鈕、自畫下載表格),清單裡也還是沒有多選批次刪。
這篇就把這塊補起來——而且用對的方式補:保住 XPages 原生刪除「存檔才生效」的好性質,只把缺的「多選」加上去。 這也是附件系列的收尾,前面談過多檔上傳與依條件刪(LotusScript)和傳統 web 用 %%Detach 勾選框刪除,這篇換 XPages。
重點摘要
- 官方只有一次刪一個:
xp:fileDownload的allowDelete是每列一個刪除連結、逐檔刪,沒有多選批次;社群也幾乎沒有現成的多選刪控制項。 - 一件反直覺的好事:XPages 原生附件刪除其實是存檔才生效(我在 Domino 12.0.2 實測),很守規矩——跟後端 LotusScript agent「還沒存就改底層」剛好相反。
- 補多選用官方 API:
NotesXspDocument.removeAttachment(欄位, 檔名),官方明寫「must save … for the change to take effect」,天生 save-bounded;配getAttachmentList列附件。 - 別掉到後端:不要用
getDocument().getAttachment(name).remove(),混用資料來源存檔與後端寫入會製造存檔衝突。 - 自己做:
xp:repeat列附件 + 每列標記(收進viewScopeMap)+ 一顆「儲存」把新上傳與被標記的刪除一次落地。
先確認一件事:原生刪除其實很守規矩
動手之前先破一個可能的誤會。你或許以為 XPages 的附件刪除是點下去就即時砍掉——其實不是。我在自己的環境(Domino 12.0.2 FP8)實測:用原生 File Download 控制項刪一個附件、但不要按存檔、直接關掉重開文件,那個檔還在。也就是說,原生刪除是跟著文件的 Save 一起 commit 的。
這點很關鍵,因為它跟前一篇 LotusScript 的後端批次刪剛好相反:後端 agent 用 Remove + Save 是直接對後端文件動刀、立刻落地,還沒等使用者確定存檔就改了底層。XPages 原生這邊反而本來就守規矩。
所以目標很清楚:保住這個「存檔才生效」的性質,只把缺的「多選」補上,而不是去救什麼正確性。
官方地基:removeAttachment 與 getAttachmentList
補多選,用的是 XPages 資料來源(xp:dominoDocument)自己的 API,不必掉到後端。
列出附件——getAttachmentList(欄位名),官方回一個 java.util.List、每個元素是 NotesEmbeddedObject:
var atts = doc.getAttachmentList("Body"); // List<NotesEmbeddedObject>// 官方範例:用 iterator 跑、對每個 element 取 .getName()刪一個——removeAttachment(欄位名, 檔名),回傳 boolean,而且官方頁寫得很直白:「You must save the document for the change to take effect in the data store.」
doc.removeAttachment("Body", "report.pdf"); // 要 doc.save() 才真的落地看到那句「must save … to take effect」了嗎?這正是我們要的:removeAttachment 本來就存檔才生效,跟原生刪除、跟傳統 web 的 %%Detach 是同一個「commit on save」語義。用它補多選,等於延續原生的好性質,而不是打破它。
一個要避開的坑:不要為了方便掉到後端 doc.getDocument().getAttachment(name).remove() 去刪。assono 有一篇講得很清楚——在同一份文件上混用 XPages 資料來源的 save 與後端 Document 的寫入,XPages runtime 靠時間戳比對差異,會把它存成存檔衝突文件。留在資料來源這層用 removeAttachment,就不會製造衝突。
動手做:勾選多個、存檔一次刪
UI 很單純:一個 xp:repeat 跑 getAttachmentList、每列一個「刪除」鈕當標記,勾中的檔名收進一個 viewScope 的 Map;最後一顆「儲存」把新上傳與被標記的刪除一次存檔送出。
核心的刪除邏輯(放在那顆儲存鈕的 action 裡):
var del = viewScope.del; // key=檔名, value=Boolean(使用者標記的)var it = del.keySet().iterator();while (it.hasNext()) { var nm = it.next(); if (del.get(nm).toString() == "true") { doc.removeAttachment("Body", nm); // 標記者逐一移除 }}doc.save(); // 新上傳 + 刪除,同一次落地幾個設計重點:
- 標記存
viewScope、不當場刪:使用者點「刪除」只是把檔名標記起來(那列劃掉、可「復原」),什麼都還沒動到硬碟——直到按下「儲存」。這就是把原生的 save-bounded 語義延續到多選上。 - 上傳搭同一次存檔:
xp:fileUpload選的檔在 submit 的 Update Model 階段就掛上doc.Body,所以那顆doc.save()會把新上傳連同刪除一起存。 - 上傳後記得清空控制項:
fileUpload上傳完若不清掉它的值,下次 submit 它會把同一個檔再掛一次、Domino 幫你自動改名成重複的-2檔——這是我們實作時實際踩到、也查了半天的雷。上傳的 AJAX 完成後(onComplete)用一小段 client JS 把 file input 的value清空即可。
把這套做成一個最小 XPage,在測試庫(Domino 12.0.2)跑通。成品長這樣——勾了兩個(劃掉、按鈕變「復原」)、一個維持正常,按「儲存變更」才真的刪:

這個最小 XPage 的完整原始碼放在開源 repo(Apache-2.0):bryanHsiao/domino-xpages-multi-attachment-delete,可以直接抓下來貼進你的測試庫試跑。
兩件要知道的事
- 沒有 undo:一旦「儲存」把
removeAttachment落地,附件就真的沒了(只能靠複本/備份救)。所以「標記 → 存檔前都可反悔」這個設計不只是好看,是實實在在給使用者一道後悔的機會。 - 按檔名刪其實很安全:你可能會擔心「靠檔名刪、萬一有同名檔怎麼辦」——不會有這個問題。同一份文件不會出現兩個一模一樣的附件檔名:重名的檔在上傳時,Domino 會自動改成
foo-2、foo-3(前面那個「上傳重複變-2」的雷,就是這個機制),所以檔名本來就是唯一的,按檔名刪不會誤傷別的檔。
小結
XPages 原生的附件刪除其實做得不差——存檔才生效、守規矩,只是一次只能刪一個。缺的那塊「勾選多個、一次刪」官方沒給、社群也幾乎沒有現成的,但自己補並不難:用資料來源層的 removeAttachment(一樣存檔才生效)配 getAttachmentList,加一個 xp:repeat + 標記 + 一顆存檔鈕,就能在不破壞 save-bounded、也不製造存檔衝突的前提下做出多選批次刪。
這也是附件系列的收尾:從多檔上傳與依條件刪(LotusScript)、傳統 web 的 %%Detach 勾選刪,一路到 XPages 這個「官方沒給、自己刻」的多選批次刪。三種技術棧,同一個原則:刪除跟著使用者的存檔走。