Vaadin Flow中如何模拟锚点标签点击事件触发文件下载
问题场景
我可通过如下代码使用Upload组件将页面文件上传至本地文件系统:
return (MultiFileReceiver) (String fileName, String mimeType) -> { try { File file = new File(uploadFileDirectory,fileName); return fileOutputHandlerService.fileOutput(file); } catch (Exception e) { throw new RuntimeException(e); } };
目前无法实现指定路径的文件下载功能。已知的常规下载实现方案均基于Anchor元素,但需求是使用ContextMenu组件触发下载。已基于Grid组件添加了ContextMenu,代码如下:
fileListContext = grid.addContextMenu(); fileListContext.addItem("Download",uiListener.fileDownloadRequestListener());
已编写如下上下文菜单监听器,期望点击上下文菜单的下载项时,浏览器弹出保存路径选择框,引导用户启动下载:
private ComponentEventListener <GridContextMenu.GridContextMenuItemClickEvent<FileEntity>> fileDownloadRequestListener(){ //created stream user and anchor element. how to trigger download attribute of anchor? return selectedFile-> { StreamResource resource = new StreamResource(selectedFile.getItem().get().getFileName(), ()->fileInputHandlerService.fileInput(selectedFile.getItem().get())); anchor = new Anchor(resource,"a"); anchor.getElement().setAttribute("download",true); anchor.setVisible(true); add(anchor); }; }
当前已创建StreamResource实例与Anchor元素、设置了download属性,但不知道如何触发锚点的点击事件以完成文件下载。
实现方案
你当前写法的问题有两个:一是每次点击都动态新增Anchor元素到DOM,会造成大量冗余节点;二是元素添加后没有触发点击的逻辑,自然不会启动下载。两种可直接落地的实现方式如下:
- 方案一(优先推荐):使用Vaadin官方封装的
FileDownloader组件,不需要手动操作Anchor、不需要写自定义JS,不会被浏览器拦截下载
直接修改监听器代码即可,不需要额外提前初始化组件:private ComponentEventListener<GridContextMenu.GridContextMenuItemClickEvent<FileEntity>> fileDownloadRequestListener(){ return selectedFile -> { FileEntity target = selectedFile.getItem().orElseThrow(); // 构造选中文件对应的流资源 StreamResource downloadResource = new StreamResource( target.getFileName(), () -> fileInputHandlerService.fileInput(target) ); // 初始化下载器、绑定资源后直接触发下载 FileDownloader downloader = new FileDownloader(downloadResource); downloader.extend(getUI().orElseThrow()); // 下载触发后移除冗余扩展 downloader.remove(); }; } - 方案二:复用全局隐藏Anchor,通过JS触发点击
如果要沿用Anchor的实现逻辑,不要每次点击都新建Anchor,在视图初始化阶段提前创建一个移出视口的隐藏Anchor(注意不能用setVisible(false),否则元素不会渲染到DOM,无法触发点击):
再修改监听器逻辑,每次点击更新资源后调用JS触发点击:// 视图类全局成员,初始化时创建 private final Anchor hiddenDownloadAnchor = new Anchor(); // 视图构造/初始化方法中提前添加到页面 private void initDownloadAnchor() { hiddenDownloadAnchor.getElement().setAttribute("download", true); // 定位到视口外,不影响页面布局 hiddenDownloadAnchor.getStyle() .set("position", "fixed") .set("top", "-9999px") .set("left", "-9999px"); add(hiddenDownloadAnchor); }private ComponentEventListener<GridContextMenu.GridContextMenuItemClickEvent<FileEntity>> fileDownloadRequestListener(){ return selectedFile -> { FileEntity target = selectedFile.getItem().orElseThrow(); StreamResource downloadResource = new StreamResource( target.getFileName(), () -> fileInputHandlerService.fileInput(target) ); // 更新锚点绑定的资源 hiddenDownloadAnchor.setHref(downloadResource); // 调用前端JS触发点击,启动下载 hiddenDownloadAnchor.getElement().executeJs("this.click()"); }; }
注意:如果用Anchor方案,不要给元素设置
display: none或者visibility: hidden,部分浏览器会拦截不可见元素的点击触发的下载,用视口外定位的方式兼容性最好。
内容的提问来源于stack exchange,提问作者user7514788
相关产品推荐
相关产品推荐

