Skip to content

Commit 96dd233

Browse files
committed
Pluggable models for form builder
1 parent 05f9bb3 commit 96dd233

File tree

2 files changed

+64
-53
lines changed

2 files changed

+64
-53
lines changed

samples/KaraDemo/src/karademo/views/home/Forms.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import karademo.views.DefaultLayout
99
class Forms(val book : Book) : HtmlView(DefaultLayout()) {
1010
override fun render(context: ActionContext) {
1111
h2("Forms")
12-
formFor(book, Home.Update(), FormMethod.post) {
12+
formForBean(book, Home.Update(), FormMethod.post) {
1313

1414
table(fields) {
1515
tr {

src/KaraLib/src/kara/views/FormBuilder.kt

+63-52
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,47 @@ import org.apache.log4j.Logger
55
import kara.internal.*
66
import kara.InputType.*
77

8-
/**
9-
* Allows forms to be built based on a model object.
10-
*/
11-
class FormBuilder(val model : Any, val modelName : String = model.javaClass.getSimpleName().toLowerCase(), val formId : String = "form-${modelName}") : FORM() {
8+
public trait FormModel<P> {
9+
fun modelName(): String
10+
fun propertyValue(property: P): String
11+
fun propertyName(property: P): String
12+
}
13+
14+
class BeanFormModel(val model: Any) : FormModel<String> {
15+
val modelName = model.javaClass.getSimpleName().toLowerCase()
1216

13-
{
14-
id = formId
17+
override fun modelName(): String {
18+
return modelName
1519
}
1620

17-
val logger = Logger.getLogger(this.javaClass)!!
21+
override fun propertyValue(property: String): String {
22+
return model.propertyValue(property).toString() // TODO: Use provided parameter serialization instead of toString
23+
}
1824

19-
val modelClass = model.javaClass
25+
override fun propertyName(property: String): String {
26+
return property
27+
}
28+
}
29+
30+
/**
31+
* Allows forms to be built based on a model object.
32+
*/
33+
class FormBuilder<P>(val model : FormModel<P>) : FORM() {
34+
val logger = Logger.getLogger(this.javaClass)!!
2035

2136
/** If true, the form will have enctype="multipart/form-data" */
2237
var hasFiles : Boolean = false
2338

24-
fun propertyValue(property : String) : Any? {
39+
fun propertyValue(property: P) : String {
2540
return model.propertyValue(property)
2641
}
2742

28-
fun propertyName(property : String) : String {
29-
return "${modelName}[${property}]"
43+
fun propertyName(property: P) : String {
44+
return "${model.modelName()}[${model.propertyName(property)}]"
3045
}
3146

32-
fun propertyId(property : String) : String {
33-
return "form-${modelName}-${property}"
47+
fun propertyId(property: P) : String {
48+
return "form-${model.modelName()}-${model.propertyName(property)}"
3449
}
3550

3651
/**
@@ -51,35 +66,30 @@ class FormBuilder(val model : Any, val modelName : String = model.javaClass.getS
5166
*
5267
* @param text the text to use for the label (defaults to the property name)
5368
*/
54-
public fun labelFor(property : String, text : String? = null, classes : StyleClass? = null) {
69+
public fun labelFor(property: P, text : String? = null, classes : StyleClass? = null) {
5570
currentTag.label(
56-
text = text ?: property.decamel().capitalize(),
5771
forId=propertyId(property),
58-
c=classes)
72+
c=classes) {
73+
+(text ?: model.propertyName(property).decamel().capitalize())
74+
}
5975
}
6076

6177
/**
6278
* Creates an input of the given type for the given property.
6379
* This method should not generally be used, as all valid input types are mapped to their own methods.
6480
* It may be convenient, however, if you're trying to assign the input type programmatically.
6581
*/
66-
public fun inputFor(inputType : InputType, property : String, init : INPUT.() -> Unit = {}) {
82+
public fun inputFor(inputType : InputType, property: P, init : INPUT.() -> Unit = {}) {
6783
val value = propertyValue(property)
68-
var valueString = ""
69-
if (value != null)
70-
valueString = value.toString()
71-
currentTag.input(inputType=inputType, id=propertyId(property), name=propertyName(property), value=valueString, init=init)
84+
currentTag.input(inputType=inputType, id=propertyId(property), name=propertyName(property), value=value, init=init)
7285
}
7386

7487
/**
7588
* Creates a textarea for the given property.
7689
*/
77-
public fun textAreaFor(property : String, init : TEXTAREA.() -> Unit = {}) {
90+
public fun textAreaFor(property: P, init : TEXTAREA.() -> Unit = {}) {
7891
val value = propertyValue(property)
79-
var valueString = ""
80-
if (value != null)
81-
valueString = value.toString()
82-
currentTag.textarea(id=propertyId(property), name=propertyName(property), text=valueString, init=init)
92+
currentTag.textarea(id=propertyId(property), name=propertyName(property), text=value, init=init)
8393
}
8494

8595
/**
@@ -92,105 +102,105 @@ class FormBuilder(val model : Any, val modelName : String = model.javaClass.getS
92102
/**
93103
* Creates an input of type text for the given property.
94104
*/
95-
public fun textFieldFor(property : String, init : INPUT.() -> Unit = {}) {
105+
public fun textFieldFor(property: P, init : INPUT.() -> Unit = {}) {
96106
inputFor(InputType.text, property, init)
97107
}
98108

99109
/**
100110
* Creates an input of type password for the given property.
101111
*/
102-
public fun passwordFieldFor(property : String, init : INPUT.() -> Unit = {}) {
112+
public fun passwordFieldFor(property: P, init : INPUT.() -> Unit = {}) {
103113
inputFor(InputType.password, property, init)
104114
}
105115

106116
/**
107117
* Creates an input of type email for the given property.
108118
*/
109-
public fun emailFieldFor(property : String, init : INPUT.() -> Unit = {}) {
119+
public fun emailFieldFor(property: P, init : INPUT.() -> Unit = {}) {
110120
inputFor(InputType.email, property, init)
111121
}
112122

113123
/**
114124
* Creates an input of type tel for the given property.
115125
*/
116-
public fun telFieldFor(property : String, init : INPUT.() -> Unit = {}) {
126+
public fun telFieldFor(property: P, init : INPUT.() -> Unit = {}) {
117127
inputFor(InputType.tel, property, init)
118128
}
119129

120130
/**
121131
* Creates an input of type date for the given property.
122132
*/
123-
public fun dateFieldFor(property : String, init : INPUT.() -> Unit = {}) {
133+
public fun dateFieldFor(property: P, init : INPUT.() -> Unit = {}) {
124134
inputFor(InputType.date, property, init)
125135
}
126136

127137
/**
128138
* Creates an input of type datetime for the given property.
129139
*/
130-
public fun dateTimeFieldFor(property : String, init : INPUT.() -> Unit = {}) {
140+
public fun dateTimeFieldFor(property: P, init : INPUT.() -> Unit = {}) {
131141
inputFor(InputType.datetime, property, init)
132142
}
133143

134144
/**
135145
* Creates an input of type color for the given property.
136146
*/
137-
public fun colorFieldFor(property : String, init : INPUT.() -> Unit = {}) {
147+
public fun colorFieldFor(property: P, init : INPUT.() -> Unit = {}) {
138148
inputFor(InputType.color, property, init)
139149
}
140150

141151
/**
142152
* Creates an input of type number for the given property.
143153
*/
144-
public fun numberFieldFor(property : String, init : INPUT.() -> Unit = {}) {
154+
public fun numberFieldFor(property: P, init : INPUT.() -> Unit = {}) {
145155
inputFor(InputType.number, property, init)
146156
}
147157

148158
/**
149159
* Creates an input of type month for the given property.
150160
*/
151-
public fun monthFieldFor(property : String, init : INPUT.() -> Unit = {}) {
161+
public fun monthFieldFor(property: P, init : INPUT.() -> Unit = {}) {
152162
inputFor(month, property, init)
153163
}
154164

155165
/**
156166
* Creates an input of type range for the given property.
157167
*/
158-
public fun rangeFieldFor(property : String, init : INPUT.() -> Unit = {}) {
168+
public fun rangeFieldFor(property: P, init : INPUT.() -> Unit = {}) {
159169
inputFor(range, property, init)
160170
}
161171

162172
/**
163173
* Creates an input of type search for the given property.
164174
*/
165-
public fun searchFieldFor(property : String, init : INPUT.() -> Unit = {}) {
175+
public fun searchFieldFor(property: P, init : INPUT.() -> Unit = {}) {
166176
inputFor(search, property, init)
167177
}
168178

169179
/**
170180
* Creates an input of type time for the given property.
171181
*/
172-
public fun timeFieldFor(property : String, init : INPUT.() -> Unit = {}) {
182+
public fun timeFieldFor(property: P, init : INPUT.() -> Unit = {}) {
173183
inputFor(time, property, init)
174184
}
175185

176186
/**
177187
* Creates an input of type url for the given property.
178188
*/
179-
public fun urlFieldFor(property : String, init : INPUT.() -> Unit = {}) {
189+
public fun urlFieldFor(property: P, init : INPUT.() -> Unit = {}) {
180190
inputFor(url, property, init)
181191
}
182192

183193
/**
184194
* Creates an input of type week for the given property.
185195
*/
186-
public fun weekFieldFor(property : String, init : INPUT.() -> Unit = {}) {
196+
public fun weekFieldFor(property: P, init : INPUT.() -> Unit = {}) {
187197
inputFor(week, property, init)
188198
}
189199

190200
/**
191201
* Creates an input of type file for the given property.
192202
*/
193-
public fun fileFieldFor(property : String, init : INPUT.() -> Unit = {}) {
203+
public fun fileFieldFor(property: P, init : INPUT.() -> Unit = {}) {
194204
inputFor(file, property, init)
195205
if (!hasFiles) {
196206
hasFiles = true
@@ -201,7 +211,7 @@ class FormBuilder(val model : Any, val modelName : String = model.javaClass.getS
201211
/**
202212
* Creates a radio button for the given property and value.
203213
*/
204-
public fun radioFor(property : String, value : String, init : INPUT.() -> Unit = {}) {
214+
public fun radioFor(property: P, value : String, init : INPUT.() -> Unit = {}) {
205215
val modelValue = propertyValue(property).toString()
206216
currentTag.input(inputType=radio, id=propertyId(property), name=propertyName(property), value=value) {
207217
checked = value.equalsIgnoreCase(modelValue)
@@ -212,27 +222,28 @@ class FormBuilder(val model : Any, val modelName : String = model.javaClass.getS
212222
/**
213223
* Creates a checkbox for the given property.
214224
*/
215-
public fun checkBoxFor(property : String, init : INPUT.() -> Unit = {}) {
225+
public fun checkBoxFor(property: P, init : INPUT.() -> Unit = {}) {
216226
val modelValue = propertyValue(property)
217227
currentTag.input(inputType=checkbox, id=propertyId(property), name=propertyName(property)) {
218-
checked = modelValue == "true" || modelValue == true
228+
checked = modelValue == "true"
219229
init()
220230
}
221231
}
222232
}
223233

224-
225-
/**
226-
* Creates a [[FormBuilder]] for the given model object.
227-
*/
228-
fun BodyTag.formFor(model : Any, action : Link, formMethod : FormMethod = FormMethod.post, init : FormBuilder.(form : FormBuilder) -> Unit) : FORM {
234+
fun <P> BodyTag.formForModel(model: FormModel<P>, action : Link, formMethod : FormMethod = FormMethod.post, init : FormBuilder<P>.() -> Unit) {
229235
val builder = FormBuilder(model)
230236
builder.action = action
231237
builder.method = formMethod
232238
builder.tagStack = this.tagStack
233-
builder.init(builder)
239+
builder.init()
234240
children.add(builder)
235-
if (builder.hasFiles)
241+
242+
if (builder.hasFiles) {
236243
builder.enctype = EncodingType.multipart
237-
return builder
244+
}
245+
}
246+
247+
fun BodyTag.formForBean(bean: Any, action : Link, formMethod : FormMethod = FormMethod.post, init : FormBuilder<String>.() -> Unit) {
248+
formForModel(BeanFormModel(bean), action, formMethod, init)
238249
}

0 commit comments

Comments
 (0)