21 ModelGenerator
İlhan Subaşı edited this page 2019-11-26 10:33:35 +03:00

Since 1.2.1

ExtDirectSpring contains a simple generator that inspects java classes and creates the corresponding Javascript code for Model objects. The generator is able to create code for Ext JS 4.x, 5.x and Sencha Touch 2.x. Note: the generator has been moved into its own project named extclassgenerator.

To add the generator to your application create a @Controller class and use ModelGenerator.writeModel to write the Javascript code into the response. The important part here is the url in the @RequestMapping annotation. If the application uses Ext.Loader to load classes on demand this has to match the path to the directory where the models are normally stored.

    @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);
        }
    }

since 1.2.3 the ModelGenerator also works in a simple 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);
	    }
    }

This code writes the generated Javascript on one line. To make debugging easier writeModel supports a fifth parameter (debug). If true the generator writes the code in pretty format: ModelGenerator.writeModel(request, response, User.class, OutputFormat.EXTJS4, true);

Example

Provided with this class

    package test;

    public class User {
        private Integer id;
        private String firstName;
        private String lastName;
        private String email;
        private String city;
        
        //get and set methods
        //....
}

the generator creates this Ext JS code

    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'
        } ]
    });

The fields of the class do not have to be annotated with a special annotation if the datatype is one of the supported datatypes according to the table below. If a field uses an unsupported datatype (for example: List<Integer> ids) annotate it with a simple @ModelField. This will create a field in the model code with datatype 'auto'.

The generator tries to read all public accessible properties. If a field should not be part of the model code annotate it with @JsonIgnore. The annotations @ModelField and @ModelAssociation have precedence over @JsonIgnore. A field will appear in the model code if one of these two annotations is present despite the @JsonIgnore annotation.

As default the generator takes the fully qualified name of the class as the name of the model object and the name of the property as the name of the field.

Datatypes are mapped according to this list if not overwritten with the @ModelField annotation (see below)

JavaExt JS/Sencha Touch
Byteint
Shortint
Integerint
Longint
java.math.BigIntegerint
byteint
shortint
intint
longint
Floatfloat
Doublefloat
java.math.BigDecimalfloat
floatfloat
doublefloat
Stringstring
java.util.Datedate
java.util.Calendardate
java.util.GregorianCalendardate
java.sql.Datedate
java.sql.Timestampdate
org.joda.time.DateTimedate
org.joda.time.LocalDatedate
Booleanboolean
booleanboolean

Associations (since 1.2.3)
The generator is able to generate code for associations. To recognize associations the bean in question needs to have the @ModelAssociation annotation on the field that forms the association.

Based on this class definition:

    public class Book {
        public int id;
        @ModelAssociation(value = ModelAssociationType.HAS_MANY, model = Author.class)
        public List<Author> authors;
    }

The generator creates this 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'
      } ]
    });

Description about the @ModelAssociation annotation see below.

Validation (since 1.2.2)
The generator is able to add validation configurations. The generator reads the javax.validation and Hibernate validator annotations and creates corresponding validation configurations. The above call to ModelGenerator.writeModel does not add any validation configurations. The program has to call the method with an an additional parameter that specifies what validations should be generated (IncludeValidation.BUILTIN or IncludeValidation.ALL).

ModelGenerator.writeModel(request, response, User.class, OutputFormat.EXTJS4, IncludeValidation.BUILTIN, false);

This call only adds validations that are built into Ext JS and Sencha Touch.

JavaExt JS/Sencha Touch Code
javax.validation.constraints.NotNullpresence
org.hibernate.validator.constraints.NotEmptypresence
javax.validation.constraints.Sizelength
org.hibernate.validator.constraints.Lengthlength
javax.validation.constraints.Patternformat
org.hibernate.validator.constraints.Emailemail
ModelGenerator.writeModel(request, response, User.class, OutputFormat.EXTJS4, IncludeValidation.BUILTIN, false);

This call will all the above and the following additional validations. These validations are not built in Ext JS and Sencha Touch. You have to add the javascript code for this validations if you want to use them. Here is an example: https://github.com/ralscha/extdirectspring-demo/blob/master/src/main/webapp/ux-validations.js

JavaExt JS/Sencha Touch Code
javax.validation.constraints.DecimalMaxrange
javax.validation.constraints.DecimalMinrange
javax.validation.constraints.Maxrange
javax.validation.constraints.Minrange
javax.validation.constraints.Digitsdigits
javax.validation.constraints.Futurefuture
javax.validation.constraints.Pastpast
org.hibernate.validator.constraints.CreditCardNumbercreditCardNumber
org.hibernate.validator.constraints.NotBlanknotBlank
org.hibernate.validator.constraints.Rangerange

Part of the generator are the annotations @ModelField and @Model. With these two annotations the default behaviour can be overwritten and additional informations can be specified that are written to the model object source code.

@Model

AttributeDescription
value"Classname" of the model. See Ext.data.Model. If not present full qualified name of the class is used.
idPropertyName of the id property. See Ext.data.Model#idProperty. If not present default value of 'id' is used.
pagingSet this to true if the read method returns an instance of ExtDirectStoreResult. This adds reader: { root: 'records' } to the proxy configuration
readMethodSpecifies 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 directFn instead.
createMethodSpecifies the create method. This is a ExtDirect reference in the form action.methodName. See Ext.data.proxy.Direct#api.
updateMethodSpecifies the update method. This is a ExtDirect reference in the form action.methodName. See Ext.data.proxy.Direct#api.
destroyMethodSpecifies 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

@ModelField

AttributeDescription
valueName of the field. Property name. If not present the name of the property is used.
typeType of the field. Property type. If not specified the library uses the list above to determine the type.
defaultValueDefault value. Property defaultValue.
dateFormatSpecifies 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.
useNullIf 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). Property useNull.Only used if type of field is int, float, string or boolean.
mappingTypical use for a virtual field to extract field data from the model object. Property 'mapping' in JS.
persistPrevent 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.
convertFunction 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.

@ModelAssociation

AttributeValid forDescription
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.

Example

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
	}

Sencha Touch 2.x model objects

    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"
        } ]
      }
    });