Skip to content
İlhan Subaşı edited this page Nov 26, 2019 · 21 revisions

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)

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

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.

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 email
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

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

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

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 directFn instead.
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

@ModelField

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). Property useNull.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.

@ModelAssociation

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.

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