/
Field.scala
483 lines (382 loc) · 15.4 KB
/
Field.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
/*
* Copyright 2007-2011 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.liftweb
package record
import net.liftweb.common._
import net.liftweb.http.js.{JsExp}
import net.liftweb.json.JsonAST.{JNothing, JNull, JString, JValue}
import net.liftweb.util._
import scala.reflect.Manifest
import scala.xml._
import http.SHtml
/** Base trait of record fields, with functionality common to any type of field owned by any type of record */
trait BaseField extends FieldIdentifier with util.BaseField {
private[record] var fieldName: String = _
private[record] var dirty = false
protected def dirty_?(b: Boolean) = dirty = b
def resetDirty {
if (safe_?) dirty_?(false)
}
def dirty_? : Boolean = dirty
/**
* Should the field be ignored by the OR Mapper?
*/
def ignoreField_? = false
/**
* Is the value of this field optional (e.g. NULLable)?
*/
def optional_? = false
/**
* The text name of this field
*/
def name: String = fieldName
/**
* Can the value of this field be read without obscuring the result?
*/
def canRead_? : Boolean = safe_? || checkCanRead_?
/**
* If the owner is not in "safe" mode, check the current environment to see if
* the field can be read
*/
def checkCanRead_? = true
/**
* Can the value of this field be written?
*/
def canWrite_? : Boolean = safe_? || checkCanWrite_?
/**
* If the owner is not in "safe" mode, check the current environment to see if
* the field can be written
*/
def checkCanWrite_? = true
/**
* Convert the field value to an XHTML representation
*/
def toXHtml: NodeSeq = Text(toString)
/**
* Generate a form control for the field
*/
def toForm: Box[NodeSeq]
/**
* Returns the field's value as a valid JavaScript expression
*/
def asJs: JsExp
/** Encode the field value into a JValue */
def asJValue: JValue
/**
* What form elements are we going to add to this field?
*/
def formElemAttrs: Seq[SHtml.ElemAttr] = Nil
/**
* Set the name of this field
*/
private[record] final def setName_!(newName : String) : String = {
if (safe_?) fieldName = newName
fieldName
}
/**
* The error message used when the field value could not be set
*/
def noValueErrorMessage : String = "Value cannot be changed"
/**
* The error message used when the field value must be set
*/
def notOptionalErrorMessage : String = "Value required"
def tabIndex: Int = 1
override def uniqueFieldId: Box[String] = Full(name+"_id")
def label: NodeSeq = uniqueFieldId match {
case Full(id) => <label for={id}>{displayName}</label>
case _ => NodeSeq.Empty
}
def asString: String
def safe_? : Boolean = true // let owned fields make it unsafe some times
}
/** Refined trait for fields owned by a particular record type */
trait OwnedField[OwnerType <: Record[OwnerType]] extends BaseField {
/**
* Return the owner of this field
*/
def owner: OwnerType
/**
* Are we in "safe" mode (i.e., the value of the field can be read or written without any security checks.)
*/
override final def safe_? : Boolean = owner.safe_?
}
/** Refined trait for fields holding a particular value type */
trait TypedField[ThisType] extends BaseField {
/*
* Unless overriden, MyType is equal to ThisType. Available for
* backwards compatibility
*/
type MyType = ThisType // For backwards compatability
type ValidationFunction = ValueType => List[FieldError]
private[record] var data: Box[MyType] = Empty
private[record] var needsDefault: Boolean = true
/**
* Helper for implementing asJValue for a conversion to an encoded JString
*
* @param encode function to transform the field value into a String
*/
protected def asJString(encode: MyType => String): JValue =
valueBox.map(v => JString(encode(v))) openOr (JNothing: JValue)
/** Decode the JValue and set the field to the decoded value. Returns Empty or Failure if the value could not be set */
def setFromJValue(jvalue: JValue): Box[MyType]
/**
* Helper for implementing setFromJValue for a conversion from an encoded JString
*
* @param decode function to try and transform a String into a field value
*/
protected def setFromJString(jvalue: JValue)(decode: String => Box[MyType]): Box[MyType] = jvalue match {
case JNothing|JNull if optional_? => setBox(Empty)
case JString(s) => setBox(decode(s))
case other => setBox(FieldHelpers.expectedA("JString", other))
}
def validations: List[ValidationFunction] = Nil
/** Validate this field's setting, returning any errors found */
def validate: List[FieldError] = runValidation(valueBox)
/** Helper function that does validation of a value by using the validators specified for the field */
protected def runValidation(in: Box[MyType]): List[FieldError] = in match {
case Full(_) => validations.flatMap(_(toValueType(in))).distinct
case Empty => Nil
case Failure(msg, _, _) => Text(msg)
}
protected implicit def boxNodeToFieldError(in: Box[Node]): List[FieldError] =
in match {
case Full(node) => List(FieldError(this, node))
case _ => Nil
}
protected implicit def nodeToFieldError(node: Node): List[FieldError] =
List(FieldError(this, node))
protected implicit def boxNodeFuncToFieldError(in: Box[MyType] => Box[Node]):
Box[MyType] => List[FieldError] =
param => boxNodeToFieldError(in(param))
/** The default value of the field when no value is set. Must return a Full Box unless optional_? is true */
def defaultValueBox: Box[MyType]
/**
* Convert the field to a String... usually of the form "displayName=value"
*/
def asString = displayName + "=" + data
def obscure(in: MyType): Box[MyType] = Failure("value obscured")
def setBox(in: Box[MyType]): Box[MyType] = synchronized {
needsDefault = false
val oldValue = valueBox
data = in match {
case _ if !canWrite_? => Failure(noValueErrorMessage)
case Full(_) => set_!(in)
case _ if optional_? => set_!(in)
case (f: Failure) => set_!(f) // preserve failures set in
case _ => Failure(notOptionalErrorMessage)
}
val same = (oldValue, valueBox) match {
case (Full(ov), Full(nv)) => ov == nv
case (a, b) => a == b
}
dirty_?(!same)
data
}
// Helper methods for things to easily use mixins and so on that use ValueType instead of Box[MyType], regardless of the optional-ness of the field
protected def toValueType(in: Box[MyType]): ValueType
protected def toBoxMyType(in: ValueType): Box[MyType]
protected def set_!(in: Box[MyType]): Box[MyType] = runFilters(in, setFilterBox)
def setFilter: List[ValueType => ValueType] = Nil
/** OptionalTypedField and MandatoryTypedField implement this to do the appropriate lifting of Box[MyType] to ValueType */
protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType]
/**
* A list of functions that transform the value before it is set. The transformations
* are also applied before the value is used in a query. Typical applications
* of this are trimming and/or toLowerCase-ing strings
*/
protected def setFilterBox: List[Box[MyType] => Box[MyType]] = liftSetFilterToBox _ :: Nil
def runFilters(in: Box[MyType], filter: List[Box[MyType] => Box[MyType]]): Box[MyType] = filter match {
case Nil => in
case x :: xs => runFilters(x(in), xs)
}
/**
* Set the value of the field from anything.
* Implementations of this method should accept at least the following (pattern => valueBox)
* - value: MyType => setBox(Full(value))
* - Some(value: MyType) => setBox(Full(value))
* - Full(value: MyType) => setBox(Full(value))
* - (value: MyType)::_ => setBox(Full(value))
* - s: String => setFromString(s)
* - Some(s: String) => setFromString(s)
* - Full(s: String) => setFromString(s)
* - null|None|Empty => setBox(defaultValueBox)
* - f: Failure => setBox(f)
* And usually convert the input to a string and uses setFromString as a last resort.
*
* Note that setFromAny should _always_ call setBox, even if the conversion fails. This is so that validation
* properly notes the error.
*
* The method genericSetFromAny implements this guideline.
*/
def setFromAny(in: Any): Box[MyType]
/** Generic implementation of setFromAny that implements exactly what the doc for setFromAny specifies, using a Manifest to check types */
protected final def genericSetFromAny(in: Any)(implicit m: Manifest[MyType]): Box[MyType] = in match {
case value if m.erasure.isInstance(value) => setBox(Full(value.asInstanceOf[MyType]))
case Some(value) if m.erasure.isInstance(value) => setBox(Full(value.asInstanceOf[MyType]))
case Full(value) if m.erasure.isInstance(value) => setBox(Full(value.asInstanceOf[MyType]))
case (value)::_ if m.erasure.isInstance(value) => setBox(Full(value.asInstanceOf[MyType]))
case (value: String) => setFromString(value)
case Some(value: String) => setFromString(value)
case Full(value: String) => setFromString(value)
case (value: String)::_ => setFromString(value)
case null|None|Empty => setBox(defaultValueBox)
case (failure: Failure) => setBox(failure)
case Some(other) => setFromString(String.valueOf(other))
case Full(other) => setFromString(String.valueOf(other))
case other => setFromString(String.valueOf(other))
}
/**
* Set the value of the field using some kind of type-specific conversion from a String.
* By convention, if the field is optional_?, then the empty string should be treated as no-value (Empty).
* Note that setFromString should _always_ call setBox, even if the conversion fails. This is so that validation
* properly notes the error.
*
* @return Full(convertedValue) if the conversion succeeds (the field value will be set by side-effect)
* Empty or Failure if the conversion does not succeed
*/
def setFromString(s: String): Box[MyType]
def valueBox: Box[MyType] = synchronized {
if (needsDefault) {
needsDefault = false
data = defaultValueBox
}
if (canRead_?) data
else data.flatMap(obscure)
}
/** Clear the value of this field */
def clear: Unit = optional_? match {
case true => setBox(Empty)
case false => setBox(defaultValueBox)
}
}
trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[ThisType] {
/**
* ValueType represents the type that users will work with. For MandatoryTypeField, this is
* equal to ThisType.
*/
type ValueType = ThisType // For util.BaseField
//TODO: fullfil the contract of Product1[ThisType]
def canEqual(a:Any) = false
def _1 = value
override def optional_? = false
/**
* Set the value of the field to the given value.
* Note: Because setting a field can fail (return non-Full), this method will
* return defaultValue if the field could not be set.
*/
def set(in: MyType): MyType = setBox(Full(in)) openOr defaultValue
def toValueType(in: Box[MyType]) = in openOr defaultValue
def toBoxMyType(in: ValueType) = Full(in)
def value: MyType = valueBox openOr defaultValue
def get: MyType = value
def is: MyType = value
protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = in.map(v => setFilter.foldLeft(v)((prev, f) => f(prev)))
/**
* The default value of the field when a field has no value set and is optional, or a method that must return a value (e.g. value) is used
*/
def defaultValue: MyType
def defaultValueBox: Box[MyType] = if (optional_?) Empty else Full(defaultValue)
override def toString = valueBox match {
case Full(null)|null => "null"
case Full(v) => v.toString
case _ => defaultValueBox.map(v => if (v != null) v.toString else "null") openOr ""
}
}
trait OptionalTypedField[ThisType] extends TypedField[ThisType] with Product1[Box[ThisType]] {
/**
* ValueType represents the type that users will work with. For OptionalTypedField, this is
* equal to Option[ThisType].
*/
type ValueType = Option[ThisType] // For util.BaseField
//TODO: fullfil the contract of Product1[ThisType]
def canEqual(a:Any) = false
def _1 = value
final override def optional_? = true
/**
* Set the value of the field to the given value.
* Note: Because setting a field can fail (return non-Full), this method will
* return defaultValueBox if the field could not be set.
*/
def set(in: Option[MyType]): Option[MyType] = setBox(in) or defaultValueBox
def toValueType(in: Box[MyType]) = in
def toBoxMyType(in: ValueType) = in
def value: Option[MyType] = valueBox
def get: Option[MyType] = value
def is: Option[MyType] = value
protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = setFilter.foldLeft(in){ (prev, f) =>
prev match {
case fail: Failure => fail //stop on failure, otherwise some filters will clober it to Empty
case other => f(other)
}
}
def defaultValueBox: Box[MyType] = Empty
override def toString = valueBox match {
case Full(null)|null => "null"
case Full(v) => v.toString
case _ => defaultValueBox.map(v => if (v != null) v.toString else "null") openOr ""
}
}
/**
* A simple field that can store and retrieve a value of a given type
*/
trait Field[ThisType, OwnerType <: Record[OwnerType]] extends OwnedField[OwnerType] with TypedField[ThisType] {
def apply(in: MyType): OwnerType = apply(Full(in))
def apply(in: Box[MyType]): OwnerType = if (owner.meta.mutable_?) {
this.setBox(in)
owner
} else {
owner.meta.createWithMutableField(owner, this, in)
}
}
/**
* Mix in to a field to change its form display to be formatted with the label aside.
*
* E.g.
* <div id={ id + "_holder" }>
* <div><label for={ id }>{ displayName }</label></div>
* { control }
* </div>
*/
trait DisplayWithLabel[OwnerType <: Record[OwnerType]] extends OwnedField[OwnerType] {
override abstract def toForm: Box[NodeSeq] =
for (id <- uniqueFieldId; control <- super.toForm)
yield
<div id={ id + "_holder" }>
<div><label for={ id }>{ displayName }</label></div>
{ control }
<lift:msg id={id} errorClass="lift_error"/>
</div>
}
trait KeyField[MyType, OwnerType <: Record[OwnerType] with KeyedRecord[OwnerType, MyType]] extends Field[MyType, OwnerType] {
def ===(other: KeyField[MyType, OwnerType]): Boolean = this.valueBox == other.valueBox
}
object FieldHelpers {
def expectedA(what: String, notA: AnyRef): Failure = Failure("Expected a " + what + ", not a " + (if (notA == null) "null" else notA.getClass.getName))
}
trait LifecycleCallbacks {
this: BaseField =>
def beforeValidation {}
def afterValidation {}
def beforeSave {}
def beforeCreate {}
def beforeUpdate {}
def afterSave {}
def afterCreate {}
def afterUpdate {}
def beforeDelete {}
def afterDelete {}
}