PDA

View Full Version : Forms architecture / best methods



Troy Wolf
30 Aug 2007, 1:26 PM
First, although my question seems like a basic, beginner question, searching the forums did not result in answers.

I've examined the Forms examples and documentation. I've read many posts with forms code examples. I've seen three distinct patterns.


Create the form (form tag) directly in the HTML markup, then create an Ext form object from it
Create the form 100% using Ext
Create the form from a DIV structure that tells Ext how to construct the form


When and why should a developer use one pattern over another? Is there an Ext progression involved? That is, is one of these patterns the "old" Ext way and one is the "new" Ext way?

I am a grasshopper, Sensei.

For what it's worth, I guess I've gotten over enough of the Ext hump to plunge into the $249 support license. Just got to access the company credit card now. So I guess if I don't get an answer here, soon I can post in the premium member help forum. For me, there was a chicken/egg thing going on...I need help to learn enough about Ext to decide if I want to use it and purchase the support, but I may need that paid support to learn enough about it....doh! I wonder how many people would purchase support, but never get past the initial learning curve and thus give up.

brian.moeskau
30 Aug 2007, 1:33 PM
It's not an Ext progression, it's just different ways of approaching the same problem. I wouldn't read too much into it. Some people have existing markup, or prefer to create markup, some want to build everything from code. Our goal is to support both methodologies to provide flexibility for the developer.

BTW, the $249 license is ONLY a license with no support. I think you want the $299 Silver support instead (http://extjs.com/ext-store). ;)

Troy Wolf
30 Aug 2007, 3:16 PM
Thanks for the license/support clarification, Brian. I was confused.

Regarding various Forms patterns, can you think of any advantages for one pattern over another? For example, if a person has a very unique and complicated form layout or one with lots of various (non-standard) help text within the form, would laying the form out using markup allow you to do things you could not accomplish with a form created 100% using Ext?

Animal
30 Aug 2007, 11:44 PM
I think it depends on the richness of your server architecture.

With JSP tags, I've gone for the road of generating complex HTML forms containing complex Ext structured and styled markup for the fields.

This is then "activated" by creating an Ext.form.BasicForm from the form element, and creating an appropriate Ext.form.Field from each input element.

The script to do this is output by the form tag handler which knows all about Ext forms, knows what input tags have been nested inside it, and outputs a largs chunk of script to bring the markup alive.

Creating a script to create everything on the fly makes it difficult to nest things properly. I have fields in columns or fieldsets, and getting a class to know about that and create the correct form.container()/form.column()/form.end() calls would be quite a challenge I imagine. Probably possible though...

Troy Wolf
31 Aug 2007, 4:50 AM
Creating a script to create everything on the fly makes it difficult to nest things properly. I have fields in columns or fieldsets, and getting a class to know about that and create the correct form.container()/form.column()/form.end() calls would be quite a challenge I imagine. Probably possible though...

This was exactly my impression when I first started learning about Ext Forms many moons ago (about 12 moons actually). I thought, "wow, checkboxes that work like normal checkboxes where you can click the box or the label, finally,the elusive combobox, ooh look at that beautiful validation alert!" After looking at some Forms code, I thought, "holy crap, how would you ever create a complex form layout". Then I learned that Ext really does provide a lot of powerful layout capability within forms, but I definitely don't see it being easier than standard markup. Of course, I'm very familiar with standard HTML/CSS markup.

Although I don't use a WYSIWYG editor, millions of web designers/developers do. Those people want to design forms by dragging and dropping form elements onto the grid and arranging as desired. So the ability to completely design a form with HTML like "normal" then apply some javascript to "Ext-ize" it may be ideal for many people.


With JSP tags, I've gone for the road of generating complex HTML forms containing complex Ext structured and styled markup for the fields.

This is then "activated" by creating an Ext.form.BasicForm from the form element, and creating an appropriate Ext.form.Field from each input element.
What I don't like about creating forms using the Ext structured DIVs is it seems I have to code twice as much. That is, I have to create the DIVs that define the form and field elements, and then I have to write javascript to apply Ext to each of those elements. The code to apply Ext to those DIVs is not much easier than creating the form from scratch in Ext javascript. Does this ring true?

By the way, I still don't get your "JSP tags". So you code all in Java and it automagically outputs the Ext javascript for your objects? It sounds like voodoo. B) You aren't anywhere near Kansas City so I can come see a demo are you?

Animal
31 Aug 2007, 5:36 AM
The code to apply Ext to those DIVs is not much easier than creating the form from scratch in Ext javascript. Does this ring true?

Yes, you'll still need to call a constructor with a config object containing all the extra Ext options. No way round this. If you want a capable wrapper widget round your input fields, you have to instantiate them.


By the way, I still don't get your "JSP tags"

The J2EE standards include a custom tag system. So my menu builder markup looks like this:



<div style="padding:10px;overflow:auto">
<aspicio:form id="menuForm">
<div style="float:left;margin-right:10px" id="menuContainer">
<aspicio:box>
<h3>Menu</h3>
<aspicio:tree
id="menu"
classname="menubuilder-tree"
enabledrag="true"
enabledrop="true"
ddgroup="menuTree"
enabledelete="true"
editable="true"
rootattributes="<%=menuBar.getJson(true, true)%>"
/>
</aspicio:box>
</div>
<div style="float:left" id="componentContainer">
<aspicio:box>
<h3>Components</h3>
<aspicio:view
id="availableComponents"
classname="menubuilder-components"
draggroup="menuTree"
select="multi"
collection="<%=components%>"
type="<%=Component.class%>"
template='<%="<div id=\\\"component_{id}\\\" class=\\\"" + Component.class.getSimpleName() + " Component\\\"><img align=\\\"top\\\" height=\\\"16px\\\" width=\\\"16px\\\" src=\\\"{entityImageUrl}\\\">{componentDescription}</div>"%>'
/>
</aspicio:box>
</div>
<input type="hidden" name="updateXML"></input>
</aspicio:form>
</div>


Plus a bit of extra script to specify exactly how to create tree nodes from the specific type of Ext.data.Record that the View is holding.

Animal
31 Aug 2007, 5:39 AM
Which looks like

http://i131.photobucket.com/albums/p286/TimeTrialAnimal/latestmenubuilder.jpg

crafter
1 Sep 2007, 3:38 AM
This is then "activated" by creating an Ext.form.BasicForm from the form element, and creating an appropriate Ext.form.Field from each input element.

Would you wind sharing a snippet of how this is done, Animal.

This would be a key peice in my list of missing links.

Animal
1 Sep 2007, 3:51 AM
I can't post the Java code that I wrote at work, but I can post an example of the generated javascript.

Fluff this thread on Monday, and I'll post a snippet.

It's a LOT of code, and, being generated, it's not pretty, but it's generated in a simple manner. Each different tag handler has knowledge coded into it (by me) about how to create an Ext representation of itself, whether its a TextField, a Checkbox, a NumberField, or a KeyField (which does xref to a DB table and pops up a Grid based on input)

You need to practice creating forms "manually" writing the code, and then you can put that knowledge into Java classes.

crafter
3 Sep 2007, 11:52 AM
I can't post the Java code that I wrote at work, but I can post an example of the generated javascript.

That's perfectly understandable. Just a snippet of the resultant code will helps tons. just enough to show how the form markup is "activated" by the BasicForm.

Animal
3 Sep 2007, 11:41 PM
OK, here's a very simple Component, the Language Maintenance page.

It only has 2 input fields. The Language Code, and the Language Name.

Here's the source JSP:



<%@ taglib uri="fclui" prefix="aspicio" %>
<%@ page language="java" pageEncoding="UTF-8"
import="java.util.Map,
com.aspicio.persistence.*,
com.aspicio.security.AspicioUser,
com.aspicio.util.PersistenceMgr,
com.aspicio.util.Utils,
com.aspicio.util.Logger,
com.aspicio.entity.base.Language"%>
<aspicio:page>
<%
Language entity = null;
final String listManagerId = request.getParameter("listManagerId");
final String entityId = request.getParameter("entityId");
Dao dao = AspicioUser.getCurrentUser().getDao();
if (entityId != null)
entity = (Language)dao.getById(Language.class, Long.parseLong(entityId));
else {
final String entityCode = request.getParameter("code");
if (entityCode != null) {
try {
entity = (Language)dao.getByCode(Language.class, entityCode);
} catch (Exception e) {
}
}
}
boolean isNew = false;
if (entity == null) {
isNew = true;
entity = new Language();
}
%>
<aspicio:form id="languageForm">
<input type="hidden" name="formEntityType" value="Language"/>
<input type="hidden" name="id" value="<%=entity.getId()%>"/>
<input type="hidden" name="version" value="<%=entity.getVersion()%>"/>
<aspicio:fieldset legend="Primary data">
<aspicio:filterfield listId="<%=listManagerId%>" entity="<%=entity%>" property="code"/>
<aspicio:textfield entity="<%=entity%>" property="name"/>
</aspicio:fieldset>
</aspicio:form>
</aspicio:page>


At the end of this post is the generated page that the browser sees.

You will not be able to run this page. The generated markup and generated script are tightly iintegrated with custom classes.

AU.initPage is a function which builds a BorderLayout in the document.body and creates the standard north, west, center and south Regions, sets up the menu according to the passed tree config, and readies the page for it's content.

The function activatePage consists of code which has been "contributed" by all the JSP tags which need to be created and activated in javascript code.

They all add their own code fragments to the overall <aspicio:page> tags handler which concats them all and puts them into this function.

The Form tags is especially complex. It collects its nested input fields, and creates an array of fields. Each input handling tag outputs an anonymous function which creates, and initializes and returns the javascripts widget, so you'll see the "fields" array looking like



var fields = [
function() {
var f = new Ext.form.TextField();
// preprocess f
return f;
}(),
function() { blah){}
];


Each field is applied to a field that the input field's tag hander output as HTML with a unique, generated ID.

We have to generate all IDs because of the nature of popup key lookup windows. Each lookup pops up the full maintenance Component for the desired table, so any page may exist in the document more than once. So no ids can be referenced. This probably happens a lot in "Ajax" apps. Dynamic loading from the same URL more than once causing id clashes. Our tags generate all ids.

In script, you can see that all the scripts are within the same scope They are executed in the scope of a ComponentPanel widget (activatePage.call(cp); where cp is the ComponentPanel, a subclass of ContentPanel) through which access to the form, and fields within that form is possible.




<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head>
<link rel="shortcut icon" href="/aspicio/images/Aspicio.ico" type="image/x-icon"/>
<script type="text/javascript" src="/aspicio/dwr/engine.js"></script>
<script type="text/javascript" src="/aspicio/dwr/interface/AspicioUtils.js"></script>
<script type="text/javascript" src="/aspicio/js/JavascriptMessages.js"></script>
<script type="text/javascript" src="/aspicio/js/Aspicio.js"></script>
<script type="text/javascript" src="/aspicio/dwr/interface/ListHelper.js"></script>
<script type="text/javascript">

var cp;
Ext.BLANK_IMAGE_URL='/aspicio/images/s.gif';
function initializePage() {
AU.initAspicio(null
,{
text:'Nige\u0027s Menu!',id:1,
className:'Menu',
iconCls: 'folder',
expanded:true,
allowDrag:false,
leaf:false,
children:[
{
text:'Static Data',id:3,
className:'Menu',
iconCls: 'folder',
expanded:false,
allowDrag:false,
leaf:false,
children:[
{
text:'EconomicGroup Maintenance',id:9,
className:'Menu',
icon:'/aspicio/images/entity/EconomicGroup.gif',
componentId:23,
href:'/aspicio/AspComponent.get?component=EconomicGroup',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'Language Maintenance',id:7,
className:'Menu',
icon:'/aspicio/images/entity/Language.gif',
componentId:21,
href:'/aspicio/AspComponent.get?component=Language',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'Person Maintenance',id:31,
className:'Menu',
icon:'/aspicio/images/entity/Person.gif',
componentId:51,
href:'/aspicio/AspComponent.get?component=Person',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'UserGroup Maintenance',id:11,
className:'Menu',
icon:'/aspicio/images/entity/UserGroup.gif',
componentId:26,
href:'/aspicio/AspComponent.get?component=UserGroup',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'TimeZone Maintenance',id:10,
className:'Menu',
icon:'/aspicio/images/entity/TimeZone.gif',
componentId:24,
href:'/aspicio/AspComponent.get?component=TimeZone',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'Country Maintenance',id:21,
className:'Menu',
icon:'/aspicio/images/entity/Country.gif',
componentId:29,
href:'/aspicio/AspComponent.get?component=Country',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'User Maintenance',id:27,
className:'Menu',
icon:'/aspicio/images/entity/User.gif',
componentId:32,
href:'/aspicio/AspComponent.get?component=User',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'Menu Maintenance',id:30,
className:'Menu',
icon:'/aspicio/images/entity/Menu.gif',
componentId:49,
href:'/aspicio/AspComponent.get?component=Menu',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
} ]
},{
text:'New Menu',id:12,
className:'Menu',
iconCls: 'folder',
expanded:false,
allowDrag:false,
leaf:false,
children:[
{
text:'Language Maintenance',id:13,
className:'Menu',
icon:'/aspicio/images/entity/Language.gif',
componentId:21,
href:'/aspicio/AspComponent.get?component=Language',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'Currency Maintenance',id:14,
className:'Menu',
icon:'/aspicio/images/entity/Currency.gif',
componentId:22,
href:'/aspicio/AspComponent.get?component=Currency',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'EconomicGroup Maintenance',id:15,
className:'Menu',
icon:'/aspicio/images/entity/EconomicGroup.gif',
componentId:23,
href:'/aspicio/AspComponent.get?component=EconomicGroup',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'TimeZone Maintenance',id:16,
className:'Menu',
icon:'/aspicio/images/entity/TimeZone.gif',
componentId:24,
href:'/aspicio/AspComponent.get?component=TimeZone',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'GeneralAnalysis Maintenance',id:17,
className:'Menu',
icon:'/aspicio/images/entity/GeneralAnalysis.gif',
componentId:25,
href:'/aspicio/AspComponent.get?component=GeneralAnalysis',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'UserGroup Maintenance',id:18,
className:'Menu',
icon:'/aspicio/images/entity/UserGroup.gif',
componentId:26,
href:'/aspicio/AspComponent.get?component=UserGroup',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'Country Maintenance',id:19,
className:'Menu',
icon:'/aspicio/images/entity/Country.gif',
componentId:29,
href:'/aspicio/AspComponent.get?component=Country',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'Player Maintenance',id:20,
className:'Menu',
icon:'/aspicio/images/entity/Player.gif',
componentId:30,
href:'/aspicio/AspComponent.get?component=Player',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
} ]
},{
text:'Security Menu',id:25,
className:'Menu',
iconCls: 'folder',
expanded:false,
allowDrag:false,
leaf:false,
children:[
{
text:'Generic Security Maintenance',id:26,
className:'Menu',
icon:'/aspicio/images/entity/GSMSecurity.gif',
componentId:46,
href:'/aspicio/AspComponent.get?component=GSMSecurity',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
} ]
},{
text:'Personal',id:288,
className:'Menu',
iconCls: 'folder',
expanded:false,
allowDrag:false,
leaf:false,
children:[
{
text:'Domain Maintenance',id:328,
className:'Menu',
icon:'/aspicio/images/entity/Domain.gif',
componentId:59,
href:'/aspicio/AspComponent.get?component=Domain',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
},{
text:'Cycling News!',id:290,
className:'Menu',
icon:'/aspicio/images/entity/Menu.gif',
href:'http://www.cyclingnews.com/',
expanded:false,
allowDrag:false,
leaf:true,
children:[
]
} ]
} ]
});
var cp=AW.ComponentPanel.getParentPanel("page-content");
cp.setComponent('LanguageDetails');
cp.setTitle('Language Details');
cp.setEntityType('Language');
cp.setParentEntityType('Language');
cp.hasPopupSubcomponents=false;
cp.hasTabSubcomponents=false;
function activatePage() {
var fields=[
function(){
var b=Ext.get('AspicioForm1188890329375FilterField0FindButton');
var f=new Ext.form.TextField({ id:'AspicioForm1188890329375FilterField0',
name:'code',
helpKey:'Language.code',
forceUpperCase:true,
validatorMask:/[A-Za-z0-9]{1,10}/,value:''}).applyTo('AspicioForm1188890329375FilterField0');
b.on('click', function(){cp.supercomponent.searchOnFieldValue('AspicioForm1188890329375FilterField0');})
f.on({});
return f;}(),
function(){var f=new Ext.form.TextField({ id:'AspicioForm1188890329375TextField1',
name:'name',
helpKey:'Language.name',
allowBlank:false,
validatorMask:/[A-Za-z0-9 ]{1,50}/,value:''}).applyTo('AspicioForm1188890329375TextField1');
f.on({});
return f;}()];
this.domForm=Ext.getDom('AspicioForm1188890329375');
this.form=new Ext.form.BasicForm(this.domForm,{
id:'AspicioForm1188890329375',
url:'null',
method:'POST'
});
Ext.ComponentMgr.register(this.form);
this.form.add.apply(this.form, fields);
this.form.on('beforeAction',function(f,a){
if((a.type == 'submit') && !f.areFieldsValid()){
aspicio.util.displayMessage('Some fields are invalid or incomplete and must be corrected before you can save any changes.', aspicio.util.ERROR);
f.highlightInvalid();
return false;
}});
Ext.get('AspicioForm1188890329375').on('submit',function(e){
e.stopEvent();
this.submit();
},this.form);

this.postProcessUI();
}//end activatePage
activatePage.call(cp);
cp.setSubcomponents([]);
AW.pageLayout.layout();
}
Ext.onReady(initializePage);
</script>
<style type="text/css">@import url("/aspicio/css/Aspicio.css");

</style></head>
<body scroll="no"
>
<div id="page-header"><div id="page-title-container" class="x-layout-panel-hd"><div id="page-titlebar">&#160;</div><div id="page-toolbar"></div></div><div id="aspicio-menu-bar"></div>
</div>
<div id="page-content">


<form class="x-form " name="AspicioForm1188890329375" id="AspicioForm1188890329375" method="POST" ><input type="hidden" name="formEntityType" value="Language"/>
<input type="hidden" name="id" value="null"/>
<input type="hidden" name="version" value="null"/>
<fieldset class="x-form-fieldset x-form-label-left " ><legend>Primary data</legend><div class="x-form-item"><label style="width:100px" for="AspicioForm1188890329375FilterField0">Code</label><div class="x-form-element asp-form-element"><input name="code" id="AspicioForm1188890329375FilterField0" class="x-form-text " style="text-transform:uppercase;" size="3" maxlength="3" ><button type="button" tabindex="-1" class="asp-drill-button" id="AspicioForm1188890329375FilterField0FindButton">Find</button></div></div>
<div class="x-form-item"><label style="width:100px" for="AspicioForm1188890329375TextField1">Name</label><div class="x-form-element asp-form-element"><input name="name" id="AspicioForm1188890329375TextField1" class="x-form-text " size="50" maxlength="50" ></div></div></fieldset></form>

</div>
<div id="page-message">
</div></body></html>


That's the code that is produced if that JSP page is requested directly from the browser's navigation bar as a main page.

The app is single page, so usually, that Component will be Ajax-loaded through an XHR. Whan that happens the page handling tag does NOT produce a full HTML document with script to create a BorderLayout etc. Instead it produces script to just put that page into the content area that was generated when the app was first loaded.

The HTML which constitutes the actual page content is the same though:



<div id="AspicioPage1188890323046" style="outline:0px none;" >
<script type="text/javascript">
var cp=AW.ComponentPanel.getParentPanel("AspicioPage1188890323046");
cp.setComponent('LanguageDetails');
cp.setTitle('Language Details');
cp.setEntityType('Language');
cp.setParentEntityType('Language');
cp.hasPopupSubcomponents=false;
cp.hasTabSubcomponents=false;
function activatePage(){
var fields=[
function(){
var b=Ext.get('AspicioForm1188890323062FilterField0FindButton');
var f=new Ext.form.TextField({ id:'AspicioForm1188890323062FilterField0',
name:'code',
helpKey:'Language.code',
forceUpperCase:true,
validatorMask:/[A-Za-z0-9]{1,10}/,value:''}).applyTo('AspicioForm1188890323062FilterField0');
b.on('click', function(){cp.supercomponent.searchOnFieldValue('AspicioForm1188890323062FilterField0'
);})
f.on({});
return f;}(),
function(){var f=new Ext.form.TextField({ id:'AspicioForm1188890323062TextField1',
name:'name',
helpKey:'Language.name',
allowBlank:false,
validatorMask:/[A-Za-z0-9 ]{1,50}/,value:''}).applyTo('AspicioForm1188890323062TextField1');
f.on({});
return f;}()];
this.domForm=Ext.getDom('AspicioForm1188890323062');
this.form=new Ext.form.BasicForm(this.domForm,{
id:'AspicioForm1188890323062',
url:'null',
method:'POST'
});
Ext.ComponentMgr.register(this.form);
this.form.add.apply(this.form, fields);
this.form.on('beforeAction',function(f,a){
if((a.type == 'submit') && !f.areFieldsValid()){
aspicio.util.displayMessage('Some fields are invalid or incomplete and must be corrected before you
can save any changes.', aspicio.util.ERROR);
f.highlightInvalid();
return false;
}});
Ext.get('AspicioForm1188890323062').on('submit',function(e){
e.stopEvent();
this.submit();
},this.form);
;
}//end activatePage
activatePage.call(cp);
cp.setSubcomponents([]);
</script>
<form class="x-form " name="AspicioForm1188890323062" id="AspicioForm1188890323062" method="POST"
><input type="hidden" name="formEntityType" value="Language"/>
<input type="hidden" name="id" value="null"/>
<input type="hidden" name="version" value="null"/>
<fieldset class="x-form-fieldset x-form-label-left " ><legend>Primary data</legend><div class="x-form-item"
><label style="width:100px" for="AspicioForm1188890323062FilterField0">Code</label><div class="x-form-element
asp-form-element"><input name="code" id="AspicioForm1188890323062FilterField0" class="x-form-text
" style="text-transform:uppercase;" size="3" maxlength="3" ><button type="button" tabindex="-1" class
="asp-drill-button" id="AspicioForm1188890323062FilterField0FindButton">Find</button></div></div>
<div class="x-form-item"><label style="width:100px" for="AspicioForm1188890323062TextField1">Name</label
><div class="x-form-element asp-form-element"><input name="name" id="AspicioForm1188890323062TextField1"
class="x-form-text " size="50" maxlength="50" ></div></div></fieldset></form>
</div>