我想创建一个动态表单,在运行时从数据库生成一个表单,但还使用运行时提供的字段(使用 JPA)在数据库服务器上创建数据存储表。我已经尽我所能尝试了一切,此时我需要帮助。我将尽可能描述性地展示我的问题在哪里,以及我到底想要实现什么。
当我在 StackOverflow 上搜索时,我发现要在 primefaces 中做一个动态表单,我必须使用 Primefaces-Extensions DynaForm。我从 primefaces-extensions 展示中的示例中借了一片叶子,并成功地实现了它,如下所示。
动态表单控制器
@PostConstruct
protected void initialize() {
model = new DynaFormModel();
// add rows, labels and editable controls
// set relationship between label and editable controls to support outputLabel with "for" attribute
// 1. row
DynaFormRow row = model.createRegularRow();
DynaFormLabel label11 = row.addLabel("Author");
DynaFormControl control12 = row.addControl(new BookProperty("OurAuthor", true), "input");
label11.setForControl(control12);
DynaFormLabel label13 = row.addLabel("ISBN");
DynaFormControl control14 = row.addControl(new BookProperty("ISBN", true), "input");
label13.setForControl(control14);
// 2. row
row = model.createRegularRow();
DynaFormLabel label21 = row.addLabel("Title");
DynaFormControl control22 = row.addControl(new BookProperty("Title", false), "input", 3, 1);
label21.setForControl(control22);
// 3. row
row = model.createRegularRow();
DynaFormLabel label31 = row.addLabel("Publisher");
DynaFormControl control32 = row.addControl(new BookProperty("Publisher", false), "input");
label31.setForControl(control32);
DynaFormLabel label33 = row.addLabel("Published on");
DynaFormControl control34 = row.addControl(new BookProperty("Published on", false), "calendar");
label33.setForControl(control34);
// 4. row
row = model.createRegularRow();
DynaFormLabel label41 = row.addLabel("Language");
DynaFormControl control42 = row.addControl(new BookProperty("Language", false), "select");
label41.setForControl(control42);
DynaFormLabel label43 = row.addLabel("Description", 1, 2);
DynaFormControl control44 = row.addControl(new BookProperty("Description", false), "textarea", 1, 2);
label43.setForControl(control44);
// 5. row
row = model.createRegularRow();
DynaFormLabel label51 = row.addLabel("Rating");
DynaFormControl control52 = row.addControl(new BookProperty("Rating", 3, true), "rating");
label51.setForControl(control52);
}
public DynaFormModel getModel() {
return model;
}
public List<BookProperty> getBookProperties() {
if (model == null) {
return null;
}
List<BookProperty> bookProperties = new ArrayList<BookProperty>();
for (DynaFormControl dynaFormControl : model.getControls()) {
bookProperties.add((BookProperty) dynaFormControl.getData());
}
return bookProperties;
}
public String submitForm() {
FacesMessage.Severity sev = FacesContext.getCurrentInstance().getMaximumSeverity();
boolean hasErrors = (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0));
RequestContext requestContext = RequestContext.getCurrentInstance();
requestContext.addCallbackParam("isValid", !hasErrors);
return null;
}
DynaForm .xhtml 定义
<h:form id="mainForm">
<h:panelGroup id="dynaFormGroup">
<p:messages id="messages" showSummary="true"/>
<pe:dynaForm id="dynaForm" value="#{dynaFormController.model}" var="data">
<pe:dynaFormControl type="input" for="txt">
<p:inputText id="txt" value="#{data.value}" required="#{data.required}" />
</pe:dynaFormControl>
<pe:dynaFormControl type="calendar" for="cal" styleClass="calendar">
<p:calendar id="cal" value="#{data.value}" required="#{data.required}" showOn="button"/>
</pe:dynaFormControl>
<pe:dynaFormControl type="select" for="sel" styleClass="select">
<p:selectOneMenu id="sel" value="#{data.value}" required="#{data.required}">
<f:selectItems value="#{dynaFormController.languages}"/>
</p:selectOneMenu>
</pe:dynaFormControl>
<pe:dynaFormControl type="textarea" for="tarea">
<p:inputTextarea id="tarea" value="#{data.value}" required="#{data.required}" autoResize="false"/>
</pe:dynaFormControl>
<pe:dynaFormControl type="rating" for="rat">
<p:rating id="rat" value="#{data.value}" required="#{data.required}"/>
</pe:dynaFormControl>
<f:facet name="buttonBar">
<p:commandButton value="Submit" action="#{dynaFormController.submitForm}"
process="dynaForm" update=":mainForm:dynaFormGroup :mainForm:inputValues"
oncomplete="handleComplete(xhr, status, args)"/>
<p:commandButton type="reset" value="Reset" style="margin-left: 5px;"/>
</f:facet>
</pe:dynaForm>
</h:panelGroup>
<p:dialog header="Input values" widgetVar="inputValuesWidget">
<p:dataList id="inputValues" value="#{dynaFormController.bookProperties}" var="bookProperty"
style="margin:10px;">
<h:outputText value="#{bookProperty.name}: #{bookProperty.formattedValue}"
style="margin-right: 10px;"/>
</p:dataList>
</p:dialog>
<h:outputScript id="dynaFormScript" target="body">
/* <![CDATA[ */
function handleComplete(xhr, status, args) {
if(args && args.isValid) {
PF('inputValuesWidget').show();
} else {
PF('inputValuesWidget').hide();
}
}
/* ]]> */
</h:outputScript>
<h:outputStylesheet id="dynaFormCSS">
/* note: trick with colspan is needed for IE8 */
.pe-dynaform-cell input,
.pe-dynaform-cell textarea,
.pe-dynaform-cell[colspan="1"] input,
.pe-dynaform-cell[colspan="1"] textarea {
width: 150px;
}
/* note: trick with colspan is needed for IE8 */
.pe-dynaform-cell.calendar input,
.pe-dynaform-cell[colspan="1"].calendar input {
width: 120px;
}
.pe-dynaform-cell.select .ui-selectonemenu {
width: 157px !important;
}
.pe-dynaform-cell.select .ui-selectonemenu .ui-selectonemenu-label {
width: 130px !important;
}
</h:outputStylesheet>
</h:form>
我正在从这个转变:。当您查看 DynaFormController 时,它是完成所有 DynaForm 定义的地方。例如,如果我想在我的表单上添加一个新字段,我不必更改 .xhtml,但我必须去 DynaFormController 并定义它。
我想要并且尝试做的是将我的表单定义存储在数据库中,这样我就不会在 DynaFormCOntroller 中定义一个新字段,而是在运行时将字段及其定义添加到数据库中,而 DynaFormController 将选择它自动。换句话说,DynaFormController 在运行时从数据库构建 DynaForm。
我有一个数据库表来存储 DynaForm 字段及其定义
我从这里捕获我的 DynaForm 定义,
我可以创建任意数量的字段及其定义,它们将存储在数据库表中。如下所示。
注意:我添加的 rowNumber 字段用于布局目的。如果你看到下面生成的表格,你会意识到 firstName 和 secondName 在同一行。如果你将它与上面的表单定义进行比较,你可以看到 firstName 和 secondName 都在第 4 行。
将所有 DynaForm 定义存储在数据库中后,我从数据库中生成下面的表单。
从数据库生成的 DynaForm
当我在上面的表单上单击提交时,我在数据库中有一个存储数据的表。
这是从数据库生成 DynaForm 的 DynaFormController。
@ManagedBean
@ViewScoped
public class DynaFormBacking implements Serializable{
private @Inject
DynaFormAttributeFacade dynaFormAttributeFacade;
private List<DynaFormAttribute> dynaFormAttributeList;
private DynaFormModel model;
private @Inject TblPersonFacade personFacade;
/**
* Creates a new instance of DynaFormBacking
*/
@PostConstruct
public void init(){
model = new DynaFormModel();
DynaFormRow row;
DynaFormLabel label;
DynaFormControl control;
int i = 0; //loop incrementer
//Number of form attributes.
int listSize = getDynaFormAttributeList().size();
for (; i < listSize;) {
//Get form attributes that belong to the same row
List<DynaFormAttribute> rowDynaFormList = dynaFormAttributeFacade.getEntityRowsGivenColumnValue(new DynaFormAttribute(), "rowNumber", getDynaFormAttributeList().get(i).getRowNumber());
//create a new row
row = model.createRegularRow();
//place labels and controls on the newly created row. If we have multiple fields on the same row, this loop will place them
for (int r = 0; r < rowDynaFormList.size(); r++) {
//place label and control on the row (This is for a single form attribute)
label = row.addLabel(rowDynaFormList.get(r).getLabelName(), rowDynaFormList.get(r).getLabelColspan(), rowDynaFormList.get(r).getLabelRowspan());
control = row.addControl(new Person(false), rowDynaFormList.get(r).getControlName(), rowDynaFormList.get(r).getControlColspan(), rowDynaFormList.get(r).getControlRowspan());
label.setForControl(control);
//increment to keep track of the number of form attributes
i++;
}
}
}
public DynaFormBacking() {
}
public DynaFormModel getModel() {
return model;
}
//This list picks the form fields and their definitions from the database
public List<DynaFormAttribute> getDynaFormAttributeList() {
dynaFormAttributeList = dynaFormAttributeFacade.findAll();
return dynaFormAttributeList;
}
public String submitForm() {
FacesMessage.Severity sev = FacesContext.getCurrentInstance().getMaximumSeverity();
boolean hasErrors = (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0));
RequestContext requestContext = RequestContext.getCurrentInstance();
requestContext.addCallbackParam("isValid", !hasErrors);
TblPerson person = new TblPerson();
person.setFirstName((String) getPersonList().get(0).getFormattedValue());
person.setSecondName((String) getPersonList().get(1).getFormattedValue());
person.setSex((String) getPersonList().get(2).getFormattedValue());
person.setDateOfBirth( (Date) getPersonList().get(3).getFormattedValue());
person.setPhone((String) getPersonList().get(4).getFormattedValue());
personFacade.create(person);
return null;
}
public List<Person> getPersonList() {
if (model == null) {
return null;
}
List<Person> personList = new ArrayList<Person>();
for (DynaFormControl dynaFormControl : model.getControls()) {
personList.add((Person) dynaFormControl.getData());
}
return personList;
}
}
这是我在上述控制器中使用的 getENTityRowsGivenColumnValue 方法的实现
/**
* This method returns Entity rows of data specific to the given column
* value - return rows when given a column name and the column value
*/
public List<T> getEntityRowsGivenColumnValue(T entity, String columnName, Object columnValue) {
List<T> resultList;
javax.persistence.criteria.CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
javax.persistence.criteria.CriteriaQuery<T> cq = cb.createQuery(entityClass);
javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
cq.select(rt).where(cb.equal(rt.get(columnName), columnValue));
javax.persistence.TypedQuery<T> q = getEntityManager().createQuery(cq);
try {
resultList = q.getResultList();
} catch (NoResultException ex) {
resultList = null;
}
return resultList;
}
这是我在上面的控制器中使用的 Person 类
@ManagedBean
@RequestScoped //Must be request scoped. otherwise, it won't work
public class Person implements Serializable{
private static final long serialVersionUID = 20120521L;
private Object value;
private boolean required;
/**
* Creates a new instance of Person
*/
public Person() {
}
public Person(boolean required) {
this.required = required;
}
public Person(Object value, boolean required) {
this.value = value;
this.required = required;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public Object getFormattedValue() {
if (value instanceof Date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
return simpleDateFormat.format(value);
}
return value;
}
}
这是我在上述控制器中使用的 TblPerson 实体类
public class TblPerson implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Size(max = 45)
@Column(name = "firstName")
private String firstName;
@Size(max = 45)
@Column(name = "secondName")
private String secondName;
@Size(max = 10)
@Column(name = "sex")
private String sex;
@Column(name = "dateOfBirth")
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
// @Pattern(regexp="^\\(?(\\d{3})\\)?[- ]?(\\d{3})[- ]?(\\d{4})$", message="Invalid phone/fax format, should be as xxx-xxx-xxxx")//if the field contains phone or fax number consider using this annotation to enforce field validation
@Size(max = 45)
@Column(name = "phone")
private String phone;
public TblPerson() {
}
public TblPerson(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSecondName() {
return secondName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof TblPerson)) {
return false;
}
TblPerson other = (TblPerson) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "ijmis.model.TblPerson[ id=" + id + " ]";
}
}
这是我在Controller中使用的DynaFormAttribute实体类
public class DynaFormAttribute implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Size(max = 45)
@Column(name = "labelName")
private String labelName;
@Column(name = "labelColspan")
private Integer labelColspan;
@Column(name = "labelRowspan")
private Integer labelRowspan;
@Size(max = 45)
@Column(name = "controlName")
private String controlName;
@Column(name = "controlColspan")
private Integer controlColspan;
@Column(name = "controlRowspan")
private Integer controlRowspan;
@Column(name = "rowNumber")
private Integer rowNumber;
public DynaFormAttribute() {
}
public DynaFormAttribute(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLabelName() {
return labelName;
}
public void setLabelName(String labelName) {
this.labelName = labelName;
}
public Integer getLabelColspan() {
return labelColspan;
}
public void setLabelColspan(Integer labelColspan) {
this.labelColspan = labelColspan;
}
public Integer getLabelRowspan() {
return labelRowspan;
}
public void setLabelRowspan(Integer labelRowspan) {
this.labelRowspan = labelRowspan;
}
public String getControlName() {
return controlName;
}
public void setControlName(String controlName) {
this.controlName = controlName;
}
public Integer getControlColspan() {
return controlColspan;
}
public void setControlColspan(Integer controlColspan) {
this.controlColspan = controlColspan;
}
public Integer getControlRowspan() {
return controlRowspan;
}
public void setControlRowspan(Integer controlRowspan) {
this.controlRowspan = controlRowspan;
}
public Integer getRowNumber() {
return rowNumber;
}
public void setRowNumber(Integer rowNumber) {
this.rowNumber = rowNumber;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof DynaFormAttribute)) {
return false;
}
DynaFormAttribute other = (DynaFormAttribute) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "ijmis.model.DynaFormAttribute[ id=" + id + " ]";
}
}
这就是我需要帮助的地方:阅读完所有这些后,您可能会问自己到底想问什么。这是我真正需要帮助的地方:
1.数据存储:我在运行时生成所有的表单字段,但是我的数据存储表是静态创建的。在其他情况下,即使在运行时创建表单字段,仅创建数据存储表中的字段也是合乎逻辑的。如何在运行时动态创建我的数据存储表,而不会使数据完整性和约束面临风险,这样我将拥有包含在运行时定义的字段的数据库表?我实际上希望我第一次部署应用程序时,例如,我将创建一个表单定义并将其存储在数据库中,从数据库生成一个动态表单,同时创建一个数据库表来存储运行时字段的数据. 这应该在第一次设置应用程序时完成。(这个问题的一部分可能归结为,我如何在运行时创建一个实体并在数据库服务器上运行它以在运行时创建一个实际的表?)
2.这个实现只允许我从用户那里捕获新数据并按给定的方式存储它。如果我想在将字段值提交到数据库之前对字段值执行一些逻辑,我该怎么办?此外,有时,在某些情况下,可能需要在表单加载时自动提供表单的某些值。例如,您可能希望生成一个员工编号,并在捕获新记录时将其显示为只读字段。通过我的实施,我将如何实现这一目标?
这是一个很长的问题,但事先感谢您的善意和深思熟虑的贡献。我保证您的贡献将有很大帮助。