primeFaces 日历控件<p:calendar> 防止编辑功能使用小结

本文介绍了如何使用PrimeFaces的<p:calendar>控件来防止用户直接编辑日期,确保输入格式正确。通过设置`readonlyInput="true"`属性,可以限制用户只能通过日历选择日期,而不能手动输入,简化了防止日期格式错误的问题。
部署运行你感兴趣的模型镜像

       不知道各位是不是也有这样的感受,当需要某个控件完成我们需要的功能,而我们又没有曾经使用过此功能时,你会怎么做呢?   我相信有相当一部分人和曾经的我相同:打开搜索引擎,在网上一顿搜,无论是有用的还是没用的,都全部拿来,再做筛选,当然了结果无非两种情况:(一)找到了,费了不少时间和精力.(二)没找到,时间也用了不少.

        这也是我写这篇博客的原因之一,以我对解决问题的真实体会,首先,提倡大家有问题直接查阅官方文档, 其次,到网上搜,希望有和我有相同功能需求的您,能找到这篇总结,避免花费太长时间还不能找到想要的结果.

1.<p:calendar>.属性:

2.完成小功能:

    为了避免在输入日期时格式出现错误,在界面上,只允许在日历控件中选择,从而完成输入,不允许编辑.

(*)xhtml代码片段:

<p:calendar    size="8"  readonlyInput="true"  value="#{user.date}" pattern="yyyy-MM-dd">                                          
                                </p:calendar>   
说明:功能的关键就在"readonlyInput="true""这么一句,是不是比在网上搜索别人的例子然后帅选来的快的多呢.


您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think>问题分析:多个页面共用一个时间组件时,由于JSF的组件树是共享的,可能会导致状态冲突。具体表现为一个页面选择的时间影响另一个页面的时间显示。 解决方案: 1. 使用不同的ID:确保每个页面上的时间组件有唯一的ID,避免JSF组件树中的ID冲突。 2. 使用不同的托管Bean:每个页面使用独立的托管Bean来管理时间状态,避免状态共享。 3. 使用视图作用域(@ViewScoped):确保每个页面的状态在视图生命周期内独立。 但是,如果多个页面确实需要共享同一个组件(例如,一个公共的日历控件),那么我们需要在托管Bean中为每个页面维护独立的时间状态。 这里提供一种解决方案:在托管Bean中为每个页面维护独立的时间属性。 假设我们有两个页面:page1.xhtml和page2.xhtml,它们都使用了同一个时间组件。我们可以在托管Bean中这样设计: ```java @Named("tool_InputBean") @ViewScoped public class ToolInputBean implements Serializable { // 为页面1设置独立的时间属性 private Date page1BeginDate; private Date page1EndDate; private Date page1MaxEndDate; // 为页面2设置独立的时间属性 private Date page2BeginDate; private Date page2EndDate; private Date page2MaxEndDate; // 根据当前页面返回对应的时间属性 public Date getBeginDate() { String viewId = getCurrentViewId(); if ("page1".equals(viewId)) { return page1BeginDate; } else if ("page2".equals(viewId)) { return page2BeginDate; } return null; } public void setBeginDate(Date beginDate) { String viewId = getCurrentViewId(); if ("page1".equals(viewId)) { this.page1BeginDate = beginDate; } else if ("page2".equals(viewId)) { this.page2BeginDate = beginDate; } } // 同样的方法实现getEndDate和setEndDate public void updateMaxEndDate() { String viewId = getCurrentViewId(); if ("page1".equals(viewId)) { // 计算page1的最大结束日期 // 设置page1MaxEndDate } else if ("page2".equals(viewId)) { // 计算page2的最大结束日期 // 设置page2MaxEndDate } } public Date getMaxEndDate() { String viewId = getCurrentViewId(); if ("page1".equals(viewId)) { return page1MaxEndDate; } else if ("page2".equals(viewId)) { return page2MaxEndDate; } return null; } private String getCurrentViewId() { FacesContext context = FacesContext.getCurrentInstance(); UIViewRoot viewRoot = context.getViewRoot(); if (viewRoot != null) { String viewId = viewRoot.getViewId(); // 从viewId中提取页面名称,例如/page1.xhtml -> 返回"page1" if (viewId.contains("page1")) { return "page1"; } else if (viewId.contains("page2")) { return "page2"; } } return null; } } ``` 在页面中,我们不需要改变组件的绑定,因为getter和setter已经根据当前页面返回对应的属性。 但是,这种方法需要为每个页面维护一套属性,如果页面很多,代码会变得冗长。 另一种更优雅的方案:使用一个Map来存储每个页面的时间状态。 ```java @Named("tool_InputBean") @ViewScoped public class ToolInputBean implements Serializable { // 使用Map来存储每个页面的时间状态 private Map<String, Date> beginDateMap = new HashMap<>(); private Map<String, Date> endDateMap = new HashMap<>(); private Map<String, Date> maxEndDateMap = new HashMap<>(); public Date getBeginDate() { String viewId = getCurrentViewId(); return beginDateMap.get(viewId); } public void setBeginDate(Date beginDate) { String viewId = getCurrentViewId(); beginDateMap.put(viewId, beginDate); } // 同样的方法实现getEndDate和setEndDate public void updateMaxEndDate() { String viewId = getCurrentViewId(); // 根据当前页面的开始日期计算最大结束日期 Date beginDate = getBeginDate(); // 计算maxEndDate的逻辑... Date maxEndDate = ...; maxEndDateMap.put(viewId, maxEndDate); } public Date getMaxEndDate() { String viewId = getCurrentViewId(); return maxEndDateMap.get(viewId); } private String getCurrentViewId() { FacesContext context = FacesContext.getCurrentInstance(); UIViewRoot viewRoot = context.getViewRoot(); if (viewRoot != null) { return viewRoot.getViewId(); } return null; } } ``` 注意:视图ID可能包含路径,例如“/page1.xhtml”,我们可以直接使用这个字符串作为键。 但是,这种方法在用户同时打开同一个页面的两个标签页时会有问题,因为视图ID相同,状态会冲突。因此,我们需要一个更细粒度的区分,比如加上会话ID和视图ID的组合。 然而,在JSF的ViewScope中,同一个用户的不同标签页会生成不同的视图,因此每个视图的ViewRoot的ID是不同的。我们可以使用视图的唯一标识:`viewRoot.getViewId()` 加上视图的状态ID(但ViewScope已经为我们隔离了)。 实际上,在ViewScope中,每个视图都会创建一个新的Bean实例,所以同一个用户打开同一个页面的两个标签页,会创建两个不同的ToolInputBean实例。因此,我们不需要在Bean内部再根据视图ID进行区分。但是,如果多个页面共享同一个Bean,那么每个页面(视图)都会有自己的Bean实例。 所以,如果我们使用ViewScope,那么每个页面(视图)都会有一个独立的Bean实例,那么问题就解决了。因此,最简单的解决方案是确保每个页面使用独立的Bean实例,即使用@ViewScoped。 但是,为什么会出现问题?可能是因为Bean的作用域设置不正确(比如使用了@SessionScoped)。所以,请检查Bean的作用域。 如果确实需要共享状态(例如,多个页面需要共享同一个时间设置),那么我们需要在Bean中区分不同的页面。但如果不需要共享,那么使用@ViewScoped即可。 因此,我建议: 1. 确保托管Bean的作用域为@ViewScoped,这样每个页面都有自己的状态。 2. 如果多个页面需要共享状态,那么使用@SessionScoped,但要在Bean内部根据页面区分状态(如上述Map方案)。 根据问题描述,多个页面公用一个时间组件,但结束时间有时选择了时间还是其他页面的时间。这明显是因为状态共享了,而它们不应该共享。所以,使用@ViewScoped应该可以解决。 如果已经使用了@ViewScoped,那么请检查是否在同一个页面中多次包含了同一个片段,导致ID冲突。如果是这样,那么需要确保每个组件的ID唯一。 在JSF中,如果同一个页面上有多个相同ID的组件,会导致不可预知的行为。因此,确保每个页面内的组件ID唯一。 所以,解决方案总结: 方案1:使用@ViewScoped - 将ToolInputBean的作用域设置为@ViewScoped,这样每个页面都会有独立的状态。 方案2:确保组件ID唯一 - 在包含时间组件的每个页面中,为组件设置唯一的ID。例如,在page1.xhtml中: <p:calendar id="page1BeginDate" ... /> <p:calendar id="page1EndDate" ... /> - 在page2.xhtml中: <p:calendar id="page2BeginDate" ... /> <p:calendar id="page2EndDate" ... /> 方案3:在托管Bean中为每个页面维护独立的状态(如上述Map方案),适用于@SessionScoped且需要共享状态的情况。 根据问题描述,我倾向于方案1和方案2的结合:使用@ViewScoped并确保页面内组件ID唯一。 但是,问题中给出的代码片段中,组件的ID是固定的(beginDate和endDate)。如果同一个页面被包含多次(例如,通过ui:include),那么就会产生ID冲突。因此,我们需要动态生成ID。 例如,使用一个参数来生成唯一的ID: ```xhtml <p:calendar id="#{param.pageId}beginDate" ... /> ``` 或者使用页面名称: ```xhtml <p:calendar id="#{view.viewId.replace('/','_')}beginDate" ... /> ``` 但是,这样可能比较麻烦。 另一种做法:将时间组件封装到一个复合组件中,这样每个实例都会有自己的命名容器,从而避免ID冲突。 鉴于问题描述,我给出一个最直接的解决方案:使用@ViewScoped并确保每个页面不使用重复的ID。如果同一个页面内需要多个时间组件,则使用不同的ID。 因此,我建议修改页面,为每个时间组件设置唯一的ID。例如: 页面1: ```xhtml <div class="ui-inputgroup" style="display:inline-block;"> <span class="ui-inputgroup-addon" style="width:90px;">开始时间 </span> <p:calendar id="page1BeginDate" pattern="yyyy-MM-dd" value="#{tool_InputBean.beginDate}" inputStyle="width:90px"> <p:ajax event="dateSelect" listener="#{tool_InputBean.updateMaxEndDate}" update="page1EndDate"/> </p:calendar> </div> <p:spacer width="5px" height="10px"/> <div class="ui-inputgroup" style="display:inline-block;"> <span class="ui-inputgroup-addon" style="width:90px;">结束时间 </span> <p:calendar id="page1EndDate" pattern="yyyy-MM-dd" value="#{tool_InputBean.endDate}" inputStyle="width:90px" maxdate="#{tool_InputBean.maxEndDate}"></p:calendar> </div> ``` 页面2: ```xhtml <div class="ui-inputgroup" style="display:inline-block;"> <span class="ui-inputgroup-addon" style="width:90px;">开始时间 </span> <p:calendar id="page2BeginDate" pattern="yyyy-MM-dd" value="#{tool_InputBean.beginDate}" inputStyle="width:90px"> <p:ajax event="dateSelect" listener="#{tool_InputBean.updateMaxEndDate}" update="page2EndDate"/> </p:calendar> </div> <p:spacer width="5px" height="10px"/> <div class="ui-inputgroup" style="display:inline-block;"> <span class="ui-inputgroup-addon" style="width:90px;">结束时间 </span> <p:calendar id="page2EndDate" pattern="yyyy-MM-dd" value="#{tool_InputBean.endDate}" inputStyle="width:90px" maxdate="#{tool_InputBean.maxEndDate}"></p:calendar> </div> ``` 同时,在托管Bean中,使用ViewScope: ```java import javax.faces.view.ViewScoped; import javax.inject.Named; @Named("tool_InputBean") @ViewScoped public class ToolInputBean implements Serializable { private Date beginDate; private Date endDate; private Date maxEndDate; // getters and setters public void updateMaxEndDate() { // 根据beginDate更新maxEndDate } } ``` 这样,每个页面都会有自己的ToolInputBean实例,因此不会相互干扰。 但是,请注意:在同一个页面内,如果有两个时间组件(比如两个相同的片段),那么它们绑定的是同一个Bean属性,所以会相互覆盖。如果同一个页面内需要多个独立的时间组件,那么必须为每个组件设置不同的Bean属性(如page1BeginDate和page2BeginDate)或者使用多个不同的托管Bean。 因此,如果同一个页面内需要多个独立的时间组件,请参考上述的Map方案,但以组件ID作为键(或者使用页面内的唯一标识)。 综上所述,解决方案如下: 1. 如果多个页面之间相互独立,则使用@ViewScoped,并且每个页面使用不同的ID(避免同一页面内多个组件的ID冲突)。 2. 如果同一页面内需要多个时间组件,则使用不同的ID,并在托管Bean中为每个ID维护独立的状态。 例如,同一页面内有两个时间组件: 页面代码: ```xhtml <!-- 第一个时间组件 --> <p:calendar id="beginDate1" binding="#{beginDate1}" ... /> <p:calendar id="endDate1" ... /> <!-- 第二个时间组件 --> <p:calendar id="beginDate2" binding="#{beginDate2}" ... /> <p:calendar id="endDate2" ... /> ``` 但是,绑定到同一个Bean属性会冲突。因此,我们需要在Bean中存储多个状态。我们可以使用一个Map,以组件的clientId为键。 但是,更简单的方法是使用两个独立的属性。 因此,请根据实际需求选择合适的方法。 由于问题描述不够详细,我提供两种场景的解决方案。 场景一:多个独立页面(每个页面一个时间组件) - 每个页面使用@ViewScoped的Bean,每个页面内时间组件的ID可以相同(因为不同页面),但为了清晰,建议使用有意义的ID(如页面名称前缀)。 场景二:同一页面内多个时间组件 - 为每个时间组件设置不同的ID。 - 在托管Bean中为每个时间组件设置独立的属性(如beginDate1, endDate1, beginDate2, endDate2)或者使用Map。 如果无法预知时间组件的数量(比如动态生成),则使用Map: ```java @ViewScoped @Named public class ToolInputBean implements Serializable { private Map<String, Date> beginDates = new HashMap<>(); private Map<String, Date> endDates = new HashMap<>(); public void setBeginDate(String componentId, Date date) { beginDates.put(componentId, date); } public Date getBeginDate(String componentId) { return beginDates.get(componentId); } // 同样为endDate提供方法 } ``` 在页面中,我们需要传递一个唯一的componentId。例如: ```xhtml <p:calendar id="beginDate" value="#{tool_InputBean.getBeginDate('component1')}"> <p:ajax event="dateSelect" listener="#{tool_InputBean.updateMaxEndDate('component1')}" ... /> </p:calendar> ``` 但是,这样不太优雅。我们可以利用组件的clientId,但需要绑定组件。 另一种做法:使用自定义组件属性。但相对复杂。 因此,我建议在同一个页面内需要多个时间组件时,使用不同的托管Bean属性,并确保ID唯一。 最后,如果问题仍然存在,请提供更多信息,例如页面的结构(是否在同一页面内包含多个时间组件?是否多个页面?托管Bean的作用域?)。 根据问题描述,我推断是多个页面共享了同一个会话作用域的Bean导致的状态冲突。因此,使用@ViewScoped是最直接的解决方案。 所以,请首先尝试将ToolInputBean的作用域改为@ViewScoped。 如果问题仍未解决,请检查页面ID唯一性。 希望这些解决方案能帮助您。</think>### 多页面共享时间组件冲突解决方案 #### 问题分析 多个页面共享同一个时间组件时,状态冲突的根本原因是: 1. **组件状态未隔离**:多个页面共享同一个后端Bean属性(`beginDate`/`endDate`) 2. **AJAX更新范围不足**:日期选择事件只更新当前组件的视图状态 3. **作用域管理不当**:未区分不同页面的时间状态 #### 完整解决方案 ##### 1. 后端改造:创建页面隔离的时间状态管理 ```java @Named("tool_InputBean") @ViewScoped public class ToolInputBean implements Serializable { // 使用页面ID作为键的时间状态映射 private final Map<String, PageDateState> dateStates = new ConcurrentHashMap<>(); // 获取当前页面的时间状态 public PageDateState getCurrentDateState() { FacesContext context = FacesContext.getCurrentInstance(); String pageId = getCurrentPageId(context); return dateStates.computeIfAbsent(pageId, k -> new PageDateState()); } // 获取当前页面ID private String getCurrentPageId(FacesContext context) { UIViewRoot viewRoot = context.getViewRoot(); return viewRoot.getViewId(); // 返回如 "/page1.xhtml" } // 页面时间状态内部类 public static class PageDateState implements Serializable { private Date beginDate; private Date endDate; private Date maxEndDate; // getters/setters } } ``` ##### 2. 前端改造:动态绑定页面专属状态 ```xhtml <!-- 修改所有时间组件的value绑定 --> <div class="ui-inputgroup" style="display:inline-block;"> <span class="ui-inputgroup-addon" style="width:90px;">开始时间</span> <p:calendar id="beginDate" pattern="yyyy-MM-dd" value="#{tool_InputBean.currentDateState.beginDate}" inputStyle="width:90px"> <p:ajax event="dateSelect" listener="#{tool_InputBean.updateMaxEndDate}" update="endDate @form"/> </p:calendar> </div> <div class="ui-inputgroup" style="display:inline-block;"> <span class="ui-inputgroup-addon" style="width:90px;">结束时间</span> <p:calendar id="endDate" pattern="yyyy-MM-dd" value="#{tool_InputBean.currentDateState.endDate}" inputStyle="width:90px" maxdate="#{tool_InputBean.currentDateState.maxEndDate}"/> </div> ``` ##### 3. 增强AJAX更新逻辑 ```java public void updateMaxEndDate() { PageDateState state = getCurrentDateState(); // 计算最大结束日期逻辑 if (state.getBeginDate() != null) { Calendar cal = Calendar.getInstance(); cal.setTime(state.getBeginDate()); cal.add(Calendar.MONTH, 3); // 例如:开始日期+3个月 state.setMaxEndDate(cal.getTime()); } else { state.setMaxEndDate(null); } // 添加JSF上下文更新 PrimeFaces.current().ajax().update("endDate"); } ``` ##### 4. 添加页面切换清理机制 ```java @PreDestroy public void cleanUp() { // 视图销毁时清理状态 dateStates.remove(getCurrentPageId(FacesContext.getCurrentInstance())); } // 或添加定时清理 @Schedule(hour = "*", minute = "*/30") public void cleanExpiredStates() { long now = System.currentTimeMillis(); dateStates.entrySet().removeIf(entry -> now - entry.getValue().getLastAccessTime() > 30 * 60 * 1000 // 30分钟未访问 ); } ``` #### 解决方案说明 1. **页面隔离状态管理**: - 使用`ConcurrentHashMap`按页面ID存储时间状态 - 每个页面有独立的`PageDateState`实例 - 通过`getCurrentPageId()`动态获取页面标识 2. **动态绑定机制**: - 前端组件绑定到`currentDateState`属性 - 自动获取当前页面对应的时间状态 - 避免不同页面间的状态污染 3. **增强的AJAX更新**: - 添加`@form`更新范围确保状态同步 - 显式更新相关组件状态 - 添加PrimeFaces直接更新调用 4. **资源清理机制**: - `@PreDestroy`清理当前页面状态 - 定时任务清理过期状态 - 防止内存泄漏 #### 备选方案:组件封装 如果需要更彻底的隔离,可以创建可复用的复合组件: ```xhtml <!-- resources/components/dateRange.xhtml --> <cc:interface> <cc:attribute name="prefix" required="true" /> </cc:interface> <cc:implementation> <div class="ui-inputgroup"> <span class="ui-inputgroup-addon">#{cc.attrs.prefix}开始时间</span> <p:calendar id="#{cc.attrs.prefix}BeginDate" ... /> </div> <div class="ui-inputgroup"> <span class="ui-inputgroup-addon">#{cc.attrs.prefix}结束时间</span> <p:calendar id="#{cc.attrs.prefix}EndDate" ... /> </div> </cc:implementation> ``` 使用方式: ```xhtml <my:dateRange prefix="page1" /> <my:dateRange prefix="page2" /> ``` #### 状态管理示意图 ```mermaid graph TD A[页面1] --> B[页面ID: /page1.xhtml] C[页面2] --> D[页面ID: /page2.xhtml] B --> E[状态映射] D --> E E --> F[/page1.xhtml: PageDateState1/] E --> G[/page2.xhtml: PageDateState2/] ``` --- ###
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值