1 2 Previous Next 17 Replies Latest reply on Jul 16, 2008 8:09 PM by sjmenden

    Dynamic CRUD with Facelets Composite templating and Custom EL

    sjmenden

      I am doing work on the $SUBJECT with good success so far.  Right now.


      Watch the attached video @ http://wiki.jboss.org/auth/wiki/DynamicCrud  if you are interested where there is also more explanation.


      My goal is to create a few simple tags which take any Entity and dissect it via Reflection to generate full CRUD and search screens.


      So far, I have 90% of the basic functionality there and working to do this, but there is a significant amount left to do, let me know if you guys are interested in this.


      -Samuel

        • 1. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
          joaobmonteiro

          Hi Samuel,


          I cant play ogg but I think your idea is outstanding. It could be very useful in any application. For sure I will try and use.


          Do you have a release plan for it?


          Thanks!

          • 2. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
            sjmenden

            http://www.videolan.org/vlc/   will play ogg.  Sorry, I'm using the recorder that comes with linux, and it only seems to want to spit out ogg, otherwise I'd do a diff format.


            As for a release, I'll put all the code up with instructions as soon as I get a few bugs worked out and a bit more functionality, probably in 2-3 weeks, maybe sooner.

            • 3. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
              joaobmonteiro

              Thanks Samuel! VLC player was the solution. The video is terrific! Your tool is very powerful.


              I am curious, how are you implementing searches? Something 'query by example'-like ?




              • 4. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                sjmenden

                I forgot, I haven't implemented searching yet on the entityList.xhtml page.  I'll probably add it soon as another tag like <crud:search entity="..." searchFields="..." /> then all that would be necessary to do is have the corresponding restrictions on the back end, but that must be done manually by the user of course.


                -Samuel

                • 5. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                  btonez

                  Haven't seen the movie, but the wiki page looks awesome!  Can you host the code on Google Code or something so we can all use and improve it?

                  • 6. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                    sjmenden

                    I probably won't put it on google code or anywhere for a while, still a lot of changes to be made now, I was planning to post a zip of the code to the wiki with instructions in the next several weeks though.


                    And, also, for more easily handling basic entities, I've refactored to be able to simply write:


                    <crud:searchFields controlKey="dog" />


                    <crud:actionButtons controlKey="dog" />


                    <crud:dataTable controlKey="dog" />



                    Everything else is inferred based on the controlKey.  The controlkey being a name that identifies the bindings for the default seam way of doing crud, ie. controlKey="person" means that the following is assumed:




                    • personHome.xhtml

                    • personList.xhtml

                    • personHome

                    • personList



                    and so on..


                    But, one would typically add additional attributes when the Entity gets more complex to to make certain fields required.


                    -Samuel

                    • 7. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                      laurent4x_x52

                      Very impressive Samuel.
                      Can't wait to see the final draft of your work.



                      Meantime I got a request, as I said on my 1st post on this list I got a requirement I can't success for now; (I didn't yet have the time to dive deeper in it, should be tomorrow..)


                      It's about the way to dynamically compose the form page .



                      This what you achieve here !
                      at one point, you got the list of fields you should render and their types; then how did you manage to build the components on runtime ?



                      Could you please share the basics of your architecture ?


                      I will then give it a try on my own.


                      Have a nice day ?

                      • 8. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                        sjmenden

                        I will post more code soon on the wiki, it is mainly a matter of time right now, and finding it.  I need to document everything so far, both code inline and how to use it.  Here is a bit of an explanation on Dynamic attributes however:


                        Dynamic Attribute Binding:


                        This allows the following <crud:edit ... eyeColorEnum="#{eyeColors}" eyeColorEnumLabel="label" ... />  where eyeColorEnum and eyeColorEnumLabel are not known to the composite template in the backend.  This is accomplished via a TagHandler which exposes the attribute in the FaceletsContext (VariableMapper is private and not accessible, has to be a better way than forcing it) to the the FacesContext.  This allows the attributes to be references via EL in the composite template.


                        Continuing the example above, eyeColorEnum gets exposed to the FacesContext in the edit.xhtml composite template here: <e:setPropertyBindings valueBinding="#{field.name}" endsWith="Enum"/>.  Meaning, look for a variable in the Facelets VariableMapper that ends with Enum, and starts with #{field.name} which #{field.name} resolves to eyeColor, and put that in the FacesContext.


                        Then the following with comments inline:



                        <e:isEnum id="vb">
                            <!-- Bind the var enum to the previously exposed value ie eyeColorEnum -->
                            <e:setValueBinding var="enum" valueBinding="#{e:evalEl(e:concat(field.name, 'Enum'))}"/>
                            <!-- Bind eyeColorEnumLabel like above-->
                            <e:setValueBinding var="enumLabel" valueBinding="#{e:evalEl(e:concat(field.name, 'EnumLabel'))}"/>
                            
                            <!-- Now the value can just be referenced like always #{enum} and the label can be referenced via #{e[enumLabel]} which is standard composite EL -->
                            <h:selectOneMenu value="#{entity[field.name]}">
                                        <s:selectItems value="#{enum}" var="e" label="#{e[enumLabel]}" noSelectionLabel="Please select" />
                                        <s:convertEnum />
                            </h:selectOneMenu>
                        </e:isEnum>




                        -Samuel

                        • 9. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                          laurent4x_x52

                          Thanks;
                          I think first I need to get back and read back about TagHandler ; FacesContext ...


                          it's clear I can't go forwards with so less knowledge on this.



                          By the way 
                          I've put a link towards your thread on
                          My former post (GenerateADynamicFormAdviceNeeded) as many people will be more than happy to read this I think;


                          Your work is impressive Samuel, sincerly !


                          Cheers.

                          • 10. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                            sjmenden

                            As a Friday present I am going to post code to the composite template that handles generating a CRUD page from an entity.  The biggest reason I'm not just throwing all the code up here is that I don't want people to start using it in it's current state, there is a lot of cleaning up and renaming, and packaging, ect... to do, but, this is a teaser:



                            <ui:composition  xmlns="http://www.w3.org/1999/xhtml"
                                             xmlns:ui="http://java.sun.com/jsf/facelets"
                                             xmlns:h="http://java.sun.com/jsf/html"
                                             xmlns:f="http://java.sun.com/jsf/core"
                                             xmlns:s="http://jboss.com/products/seam/taglib"
                                             xmlns:rich="http://richfaces.org/rich"
                                             xmlns:a="http://richfaces.org/a4j"
                                             xmlns:e="http://org.el.func/SeamFunc"
                                             xmlns:c="http://java.sun.com/jstl/core">
                                                 
                                <c:if test="${empty entity}">
                                    <c:set var="entity" value="#{e:evalEl(e:concat(controlKey, 'Home.instance'))}" />
                                </c:if>
                                
                                <c:if test="${empty excludedFields}">
                                    <c:set var="excludedFields" value="version, id" />
                                </c:if>
                                
                                <c:if test="${empty requiredFields}">
                                    <c:set var="requiredFields" value="" />
                                </c:if>
                                
                                <!-- Default perform ajax actions unless overridded -->
                                <c:if test="${empty ajax}">
                                    <c:set var="ajax" value="true" />
                                </c:if>
                                
                                <c:if test="${empty eventsQueue}">
                                    <c:set var="eventsQueue" value="#{controlKey}Queue" />
                                </c:if>
                                
                                <c:forEach items="#{entity.getClass().getDeclaredFields()}" var="field" >
                                            <s:fragment rendered="#{!e:csvContains(excludedFields, field.name)}">
                                            
                                                <s:decorate id="#{field.name}Decorate" template="../edit.xhtml">
                                                    
                                                    <ui:define name="label">#{e:splitCapitalizedWords(e:capitalize(field.name))}:</ui:define>
                                                    <e:setValueBinding var="vb" valueBinding="#{entity[field.name]}"/>
                                                    
                                                    <!-- setProperyBindings exposes variables in the FaceletContext variable mapper to the FacesContext  -->
                                                    <e:setPropertyBindings valueBinding="#{field.name}" endsWith="List"/>
                                                    <e:setPropertyBindings valueBinding="#{field.name}" endsWith="ListLabel"/>
                                                    <e:setPropertyBindings valueBinding="#{field.name}" endsWith="Enum"/>
                                                    <e:setPropertyBindings valueBinding="#{field.name}" endsWith="EnumLabel"/>
                                                    <e:setPropertyBindings valueBinding="#{field.name}" endsWith="DatePattern"/>
                                                    
                                                    <!-- Move to isList -->
                                                    <e:setValueBinding var="list" valueBinding="#{e:evalEl(e:concat(field.name, 'List'))}"/>
                                                    
                                                    <e:isDate id="vb">
                                                            <h:inputText id="#{field.name}" value="#{entity[field.name]}" required="#{e:csvContains(requiredFields, field.name)}">
                                                                    <s:convertDateTime pattern="#{e:evalEl(e:concat(field.name, 'DatePattern'))}"/>
                                                                    <a:support event="onblur" status="#{field.name}Status" reRender="#{field.name}Decorate" ajaxSingle="true" bypassUpdates="true" eventsQueue="#{eventsQueue}" ignoreDupResponses="true" requestDelay="200" rendered="#{ajax}"/>
                                                        </h:inputText>
                                                        <s:selectDate for="#{field.name}" startYear="1910" endYear="#{e:currentYear()}">
                                                                    <img src="img/dtpick.gif"/>
                                                        </s:selectDate>
                                                        <a:status forceId="true" id="#{field.name}Status">
                                                    <f:facet name="start">
                                                        <h:graphicImage  value="img/spinner.gif"/>
                                                    </f:facet>
                                                </a:status>
                                                    </e:isDate>
                                                    
                                                    <e:isText id="vb">
                                                        <h:inputText id="#{field.name}Input" value="#{entity[field.name]}" required="#{e:csvContains(requiredFields, field.name)}">
                                                                    <a:support event="onblur" status="#{field.name}Status" reRender="#{field.name}Decorate" ajaxSingle="true" bypassUpdates="true" eventsQueue="#{eventsQueue}" ignoreDupResponses="true" requestDelay="200" rendered="true"/>
                                                        </h:inputText>
                                                        <!-- Without forceId="true" was getting "duplicate Id for a component _viewRoot:status" -->
                                                        <a:status forceId="true" id="#{field.name}Status">
                                                    <f:facet name="start">
                                                        <h:graphicImage  value="img/spinner.gif"/>
                                                    </f:facet>
                                                </a:status>
                                                    </e:isText>
                                                    
                                                    
                                                    <e:isList id="vb">
                                                            <h:selectManyListbox value="#{entity[field.name]}">
                                                                    <!-- <s:selectItems value="\#{list}" var="e" label="\#{e[e:evalEl(e:concat(field.name, 'ListLabel'))]}"/> -->
                                                                    <!-- The above does not work on postback so I will just assume to toString value of the object (var e) which should be safe for now -->
                                                                    <s:selectItems value="#{list}" var="e" label="#{e:evalEl('e')}"/>
                                                                    <s:convertEntity />
                                                        </h:selectManyListbox>
                                                        
                                                    </e:isList>
                                                    
                                                    <e:isEnum id="vb">
                                                        <e:setValueBinding var="enum" valueBinding="#{e:evalEl(e:concat(field.name, 'Enum'))}"/>
                                                        <e:setValueBinding var="enumLabel" valueBinding="#{e:evalEl(e:concat(field.name, 'EnumLabel'))}"/>
                                                        
                                                        <h:selectOneMenu value="#{entity[field.name]}">
                                                                    <s:selectItems value="#{enum}" var="e" label="#{e[enumLabel]}" noSelectionLabel="Please select" />
                                                                    <s:convertEnum />
                                                        </h:selectOneMenu>
                                                    </e:isEnum>
                                                    
                                                    <e:isBoolean id="vb">
                                                        <h:selectBooleanCheckbox value="#{entity[field.name]}"/>
                                                    </e:isBoolean>
                                                    
                                                </s:decorate>
                                            </s:fragment>
                                </c:forEach>
                                
                            </ui:composition>


                            • 11. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                              sjmenden

                              There is more code of course to the above, but all the user needs to do is add one tag to a xhtml page that looks like:



                              <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                              "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
                              <ui:composition xmlns="http://www.w3.org/1999/xhtml"
                                              xmlns:s="http://jboss.com/products/seam/taglib"
                                              xmlns:ui="http://java.sun.com/jsf/facelets"
                                              xmlns:f="http://java.sun.com/jsf/core"
                                              xmlns:h="http://java.sun.com/jsf/html"
                                              xmlns:rich="http://richfaces.org/rich"
                                              xmlns:a="http://richfaces.org/a4j"
                                              xmlns:crud="http://enhancements.seam/jsf"
                                              template="layout/template.xhtml">
                                              
                                  <ui:define name="body">
                                      
                                              <crud:masterEdit controlKey="person"
                                                   requiredFields="firstName, email"
                                                   childrenList="#{childrenList}"
                                                   childrenListLabel="firstName"
                                                   hobbiesList="#{hobbiesList}"
                                                   hobbiesListLabel="hobbyName"
                                                   eyeColorEnum="#{eyeColors}"
                                                   eyeColorEnumLabel="label"
                                                   hairColorEnum="#{hairColors}"
                                                   hairColorEnumLabel="label"
                                                   birthDateDatePattern="MM/dd/yyyy"/>
                              
                                  </ui:define>
                                  
                              </ui:composition>






                              which then generates this screen

                              • 12. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                                sjmenden

                                Where the abridged Person entity looks like:



                                @Entity
                                public class Person implements Serializable {
                                
                                        @Id
                                        @GeneratedValue
                                        private Long id;
                                
                                        @Version
                                        private int version = 0;
                                
                                        private String firstName;
                                
                                        @Temporal(TemporalType.TIMESTAMP)
                                        private Date birthDate;
                                
                                        @Email
                                        @Column(name = "EMAIL")
                                        private String email;
                                
                                        @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
                                        private List<Person> children = new ArrayList<Person>();
                                
                                        @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
                                        private List<Hobby> hobbies = new ArrayList<Hobby>();
                                
                                        @Enumerated(EnumType.STRING)
                                        private EyeColor eyeColor;
                                
                                        @Enumerated(EnumType.STRING)
                                        private HairColor hairColor;
                                
                                        private Boolean child = false;
                                ...
                                }




                                Then, simply adding fields on the Entity cause the changes to be dynamically picked up by the custom tags, no need to edit the view, unless of course, you want to override a default behavior.



                                -Samuel

                                • 13. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                                  sjmenden

                                  Added the first release with basic instructions on how to use it: http://wiki.jboss.org/auth/wiki/DynamicCrud  email me any bugs you find.


                                  Full documentation is not up yet, but should happen this week.


                                  -Samuel

                                  • 14. Re: Dynamic CRUD with Facelets Composite templating and Custom EL
                                    sjmenden
                                    1 2 Previous Next