自1.2.1版本后支持
ExtDirectSpring包含一个简单代码生成器,根据java类生成对应的JavaScript Model对象代码。生成器可以生成Ext JS 4.x和Sencha Touch 2.x两种类库适用代码。
添加代码生成器到程序需要创建一个@Controller注释的类用ModelGenerator.writeModel把Javascript代码写到返回响应中。注意注解@RequestMapping的属性url,如果程序通过Ext.Loader按需加载,对应的路径需要跟真实要存储的路径一致。
@Controller
public class ModelController {
@RequestMapping("/app/model/User.js")
public void user(HttpServletRequest request, HttpServletResponse response) throws IOException {
ModelGenerator.writeModel(request, response, User.class, OutputFormat.EXTJS4);
//or for Sencha Touch 2
//ModelGenerator.writeModel(request, response, User.class, OutputFormat.TOUCH2);
}
}
1.2.3后ModelGenerator也可以通过一个servlet来实现:
@WebServlet(urlPatterns = "/app/model/User.js")
public class SongModelServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ModelGenerator.writeModel(request, response, User.class, OutputFormat.EXTJS4);
}
}
上面代码生成的Javascript在一行上。调试更加方便writeModel支持第五个参数(debug)。如果设置为true,生成格式化后的代码:ModelGenerator.writeModel(request,
response, User.class, OutputFormat.EXTJS4, true)。
示例:
package test;
public class User {
private Integer id;
private String firstName;
private String lastName;
private String email;
private String city;
//get and set methods
//....
}
生成Ext JS代码如下
Ext.define('test.User', {
extend: 'Ext.data.Model',
fields: [ {
name: 'id',
type: 'int'
}, {
name: 'firstName',
type: 'string'
}, {
name: 'lastName',
type: 'string'
}, {
name: 'email',
type: 'string'
}, {
name: 'city',
type: 'string'
} ]
});
如果数据类型是下表的数据类型,类的字段不需要标注特殊的注解。如果字段用了一个不支持的数据类型,需要用@ModelField注解字段。在生成的model代码的数据类型为auto。
代码生成器试图转换所有可以访问的公共属性。如果某个字段不想被转换,可以在字段添加@JsonIgnore注释。注释@ModelField和@ModelAssociation优先于@JsonIgnore。如果这两个注解之一存在,字段将会在生成的代码中,不管是否存在@JsonIgnore注解。
默认情况下,生成model对象name属性用被生成类的完全限定名,属性名对应字段的名称。
字段没有@ModelField注释,数据类型根据下面列表映射。
Java | Ext JS/Sencha Touch |
---|---|
Byte | int |
Short | int |
Integer | int |
Long | int |
java.math.BigInteger | int |
byte | int |
short | int |
int | int |
long | int |
Float | float |
Double | float |
java.math.BigDecimal | float |
float | float |
double | float |
String | string |
java.util.Date | date |
java.util.Calendar | date |
java.util.GregorianCalendar | date |
java.sql.Date | date |
java.sql.Timestamp | date |
org.joda.time.DateTime | date |
org.joda.time.LocalDate | date |
Boolean | boolean |
boolean | boolean |
代码生成器如果生成带有关联关系的代码。为了确定生成的关联关系,需要用注释@ModelAssociation注解有关联关系的字段。
类定义:
public class Book {
public int id;
@ModelAssociation(value = ModelAssociationType.HAS_MANY, model = Author.class)
public List<Author> authors;
}
生成的Ext JS model代码:
Ext.define('Book', {
extend : 'Ext.data.Model',
fields : [ {
name : 'id',
type : 'int'
}],
associations : [ {
type : 'hasMany',
model : 'Author',
associationKey : 'authors',
foreignKey : 'book_id',
name : 'authors'
} ]
});
Validation (since 1.2.2)
代码生成器可以添加校验配置。生成器通过读取javax.validation和javax.validation注解生成对应校验配置。上面代码调用ModelGenerator.writeModel时没有生成任何校验信息。需要添加额外的参数(IncludeValidation.BUILTIN or IncludeValidation.ALL)生成有校验信息代码。
ModelGenerator.writeModel(request, response, User.class, OutputFormat.EXTJS4, IncludeValidation.BUILTIN, false);
上面代码只是添加了校验信息到生成的Ext JS或Sencha Touch代码。
Java | Ext JS/Sencha Touch Code |
---|---|
javax.validation.constraints.NotNull | presence |
org.hibernate.validator.constraints.NotEmpty | presence |
javax.validation.constraints.Size | length |
org.hibernate.validator.constraints.Length | length |
javax.validation.constraints.Pattern | format |
org.hibernate.validator.constraints.Email |
上面代码只是增加了校验信息,要想校验起作用,还需要添加校验规则代码。示例: Here is an example: https://github.com/ralscha/extdirectspring-demo/blob/master/src/main/webapp/ux-validations.js
Java | Ext JS/Sencha Touch Code |
---|---|
javax.validation.constraints.DecimalMax | range |
javax.validation.constraints.DecimalMin | range |
javax.validation.constraints.Max | range |
javax.validation.constraints.Min | range |
javax.validation.constraints.Digits | digits |
javax.validation.constraints.Future | future |
javax.validation.constraints.Past | past |
org.hibernate.validator.constraints.CreditCardNumber | creditCardNumber |
org.hibernate.validator.constraints.NotBlank | notBlank |
org.hibernate.validator.constraints.Range | range |
@Model
Attribute | Description |
---|---|
value | "Classname" of the model. See Ext.data.Model. If not present full qualified name of the class is used. |
idProperty | Name of the id property. See Ext.data.Model#idProperty. If not present default value of 'id' is used. |
paging | Set this to true if the read method returns an instance of ExtDirectStoreResult. This adds reader: { root: 'records' } to the proxy configuration |
readMethod | Specifies the read method. This is a ExtDirect reference in the form action.methodName. See Ext.data.proxy.Direct#api. If only the readMethod is specified generator will write property directFninstead. |
createMethod | Specifies the create method. This is a ExtDirect reference in the form action.methodName. See Ext.data.proxy.Direct#api. |
updateMethod | Specifies the update method. This is a ExtDirect reference in the form action.methodName. See Ext.data.proxy.Direct#api. |
destroyMethod | Specifies the destroy method. This is a ExtDirect reference in the form action.methodName. See Ext.data.proxy.Direct#api. |
messageProperty | (since 1.3.6) Specifies the messageProperty property in the reader config. See Ext.data.reader.Reader#messageProperty. |
disablePagingParameters | (since 1.3.8) When set to true the generator sets the paging parameters (pageParam, startParam, limitParam) in the proxy config to undefinded (Ext JS) or false (Touch). Defaults to false |
Attribute | Description |
---|---|
value | Name of the field. Property name. If not present the name of the property is used. |
type | Type of the field. Property type. If not specified the library uses the list above to determine the type. |
defaultValue | Default value. Property defaultValue. |
dateFormat | Specifies the format of date. Property dateFormat. For a list of all supported formats see Sencha Doc: Ext.Date. Will be ignored if the field is not a date field. |
useNull | If true null value is used if the value cannot be parsed. If false default values are used (0 for integer and float, "" for string and false for boolean). PropertyuseNull.Only used if type of field is int, float, string or boolean. |
mapping | Typical use for a virtual field to extract field data from the model object. Property 'mapping' in JS. |
persist | Prevent the value of this field to be serialized or written with Ext.data.writer.Writer. Typical use for a virtual field. Property 'persist' in JS. |
convert | Function which coerces string values in raw data into the field's type. Typical use for a virtual field. Property 'Ext.data.Field.convert' in JS. |
Attribute | Valid for | Description |
---|---|---|
value | all | Describes the type of the association. ModelAssociationType.HAS_MANY, ModelAssociationType.BELONGS_TO or ModelAssociationType.HAS_ONE. Corresponds to the type config property. |
model | all | The class of the model that this object is being associated with. If not specified the full qualified class name is used. Corresponds to the model config property. |
autoLoad | HAS_MANY | True to automatically load the related store from a remote source when instantiated. Defaults to false. Corresponds to the autoLoad config property. |
foreignKey | all | The name of the foreign key on the associated model that links it to the owner model. If missing the lowercased name of the owner class plus "_id" is used. Corresponds to the foreignKey config property. |
name | HAS_MANY | The name of the function to create on the owner model to retrieve the child store. If not specified, the name of the field is used. Corresponds to the name config property. |
primaryKey | all | The name of the primary key on the associated model. Default is "id". If the associated model is annotated with @Model(idProperty="..") this value is used. Corresponds to the primaryKey config property. |
setterName | BELONGS_TO, HAS_ONE | The name of the setter function that will be added to the local model's prototype. Defaults to 'set' + name of the field, e.g. setCategory. Corresponds to the setterName config property. |
getterName | BELONGS_TO, HAS_ONE | The name of the getter function that will be added to the local model's prototype. Defaults to 'get' + name of the field, e.g. getCategory. Corresponds to the getterName config property. |
Java classes
@Model(value = "MyApp.Bean", idProperty = "myVerySpecialId",
paging = true, readMethod = "beanService.read",
createMethod = "beanService.create", updateMethod = "beanService.update",
destroyMethod = "beanService.destroy")
public class Bean {
@ModelField("myVerySpecialId")
private int id;
private String firstName;
@NotEmpty
private String lastName;
@ModelField
private List<Integer> someIds;
@ModelField(dateFormat = "c")
@Past
private Date dateOfBirth;
@JsonIgnore
private String password;
@ModelField
@JsonIgnore
@Email
private String email;
@ModelField(type = ModelType.STRING, useNull = true)
private Long something;
@ModelField(value = "active", defaultValue = "true")
private boolean flag;
@ModelField(persist = true)
@DecimalMax("500000")
private BigInteger bigValue;
@ModelField(mapping = "bigValue", persist = false, convert = "function(v, record) { return (record.raw.bigValue > 1000000);}")
private boolean aBooleanVirtual;
@ModelAssociation(value = ModelAssociationType.HAS_MANY, model = OtherBean.class, autoLoad = true)
public List<OtherBean> otherBeans;
//get/set methods
}
@Model(value = "MyApp.OtherBean")
public class OtherBean {
private int id;
private int bean_id;
@JsonIgnore
@ModelAssociation(value = ModelAssociationType.BELONGS_TO)
private Bean bean;
//get/set methods
}
Ext.define("MyApp.Bean",
{
extend : "Ext.data.Model",
config : {
idProperty : "myVerySpecialId",
fields : [ {
name : "myVerySpecialId",
type : "int"
}, {
name : "firstName",
type : "string"
}, {
name : "lastName",
type : "string"
}, {
name : "someIds",
type : "auto"
}, {
name : "dateOfBirth",
type : "date",
dateFormat : "c"
}, {
name : "email",
type : "string"
}, {
name : "something",
type : "string",
useNull : true
}, {
name : "active",
type : "boolean",
defaultValue : true
}, {
name : "bigValue",
type : "int"
}, {
name : "aBooleanVirtual",
type : "boolean",
mapping : "bigValue",
persist : false,
convert : function(v, record) { return (record.raw.bigValue > 1000000);}
} ],
associations : [ {
type : "hasMany",
model : "MyApp.OtherBean",
associationKey : "otherBeans",
foreignKey : "bean_id",
primaryKey : "myVerySpecialId",
autoLoad : true,
name : "otherBeans"
} ],
validations : [ {
type : "presence",
field : "lastName"
}, {
type : "past",
field : "dateOfBirth"
}, {
type : "email",
field : "email"
}, {
type : "range",
field : "bigValue",
max : 500000
} ],
proxy : {
type : "direct",
idParam : "myVerySpecialId",
api : {
read : beanService.read,
create : beanService.create,
update : beanService.update,
destroy : beanService.destroy
},
reader : {
root : "records"
}
}
}
});
Ext.define("MyApp.OtherBean",
{
extend : "Ext.data.Model",
config : {
fields : [ {
name : "id",
type : "int"
}, {
name : "bean_id",
type : "int"
} ],
associations : [ {
type : "belongsTo",
model : "MyApp.Bean",
associationKey : "bean",
foreignKey : "bean_id",
primaryKey : "myVerySpecialId",
setterName : "setBean",
getterName : "getBean"
} ]
}
});
ModelGeneratorAPT
自1.3.1以上
ExtDirectSpring包含一个以ModelGenerator为基础的APT处理器。该处理器在编译时创建的Javascript模型文件。
在Maven项目中添加以下配置到pom.xml中。插件apt-maven-plugin在maven生命周期generate-resources时自动运行。在命令行运行mvn generate-resources可以触发生命周期generate-resources阶段的运行。
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.0.8</version>
<executions>
<execution>
<id>modelgen</id>
<goals>
<goal>process</goal>
</goals>
<configuration>
<processor>ch.ralscha.extdirectspring.generator.ModelAnnotationProcessor</processor>
<outputDirectory>src/main/webapp/app</outputDirectory>
<options>
<!-- Add options here. See description below -->
</options>
</configuration>
</execution>
</executions>
</plugin>
ModelAnnotationProcessor创建一个Javascript文件为每一个注解了@Model的Java类到输出目录。
当注解@Model没有包含一个value属性,处理器直接生成Javascript文件到输出目录,用类名作为文件名。
src/main/webapp/app/Club.js
@Entity
@Model
public class Club extends AbstractPersistable {
...
}
当存在一个value属性时,根据设置的值处理器创建文件和子目录。处理器会忽略掉字符串的第一部分(MyApp),文件名是字符串的最后一部分(Team),创建子目录用字符串中间的部分(model)。
src/main/webapp/app/model/Team.js
@Entity
@Model(value = "MyApp.model.Team")
public class Club extends AbstractPersistable {
...
}
src/main/webapp/app/model/part/Team.js
@Entity
@Model(value = "MyApp.model.part.Team")
public class Club extends AbstractPersistable {
...
}
Options
ModelAnnotationProcessor支持下面的这些选项
The ModelAnnotationProcessor supports these options
Option | Description |
---|---|
<debug>true</debug> | Writes the Javascript in a pretty, readable format. DEFAULT |
<debug>false</debug> | Writes the Javascript on one line. |
<outputFormat>extjs4</outputFormat> | Writes the model class in ExtJs4 format.DEFAULT |
<outputFormat>touch2</outputFormat> | Writes the model class in Touch2 format. |
<includeValidation>none</includeValidation> | Does not include any validation code.DEFAULT |
<includeValidation>builtin</includeValidation> | Includes validation code but only validations that are built into Ext JS and Sencha Touch. |
<includeValidation>all</includeValidation> | Includes all validation code. |
<createBaseAndSubclass>true</createBaseAndSubclass> (since 1.3.2) | Creates two files (base and subclass). It does not overwrite the subclass file if it exists. Example /* ModelBase.js */ Ext.define('MyApp.model.ModelBase', { extend: 'Ext.data.Model', fields: [ ... ] }); /* Model.js */ Ext.define('MyApp.model.Model', { extend: 'MyApp.model.ModelBase }); |
<useSingleQuotes>true</useSingleQuotes> (since 1.3.2) | Writes single quotes instead of double quotes (default) |
<surroundApiWithQuotes>true</surroundApiWithQuotes> (since 1.3.2) | Surrounds the values of the proxy/api object with quotes proxy : { type : "direct", api : { read : "userService.read", destroy : "userService.destroy" } } |