Permalink
Browse files

codereview: meta on forms

  • Loading branch information...
szimano committed Feb 24, 2015
1 parent 5916829 commit 946df163f1da00af5a5bae1219cbc1cf99254993
View
@@ -15,8 +15,7 @@
"supler",
"supler-js",
"target",
"*.sh",
"README.md"
"*.sh"
],
"dependencies": {
@@ -18,5 +18,5 @@ For example, to render a text field as a password::
Supported render hints:
* for subforms: ``asList()`` (default), ``asTable()``
* for text fields: ``asPassword()``, ``asTextarea(rows = 10)``
* for text fields: ``asPassword()``, ``asTextarea(rows = 10)``, ``asHidden()``
* for single-select fields: ``asRadio()``
View
@@ -0,0 +1,36 @@
Backend: form with meta
=======================
In some cases you will find it useful to send some payload with the form that you should be able to extract before processing
the form with object.
One good example is sending an entity id of the object backing the form, so that on each POST you will be able to pull
it from the database and apply changes that came from the frontend.
Add meta on form creation
-------------------------
When creating the form use the withMeta syntax::
case class Person(id: String, name: String)
val personForm = form[Person](f => List(
f.field(_.name).label("Name")
))
personForm(person).withMeta("id", person.id).generateJSON
which will add the needed payload to the form.
Extract the meta on form posts
------------------------------
The meta can be later extracted on POST calls with::
val id = personForm.getMeta(jsonBody)("id")
val person = personDao.lookup(id)
personForm(person).process(jsonBody).generateJSON
where jsonBody is the serialized object that comes from the frontend.
View
@@ -50,6 +50,7 @@ Complete documentation
backend/formdef/static
backend/formdef/conditional
backend/formwithobject
backend/meta
frontend/rendering
frontend/clientsideval
frontend/serializing
@@ -18,6 +18,6 @@ class SuplerAttributes {
static PATH = 'supler:path';
}
class Sections {
class FormSections {
static META = 'supler_meta';
}
@@ -21,12 +21,15 @@ class CreateFormFromJson {
private generateMeta(meta:any) {
if (meta) {
var html = '<div class="supler_meta">\n';
var html = '<span class="supler_meta" style="display: none; visibility: hidden">\n';
Util.foreach(meta, (metaKey, metaValue) => {
html += "<input type='hidden' " + SuplerAttributes.FIELD_NAME + "='" + metaKey + "' value='" + metaValue + "' " +
SuplerAttributes.FIELD_TYPE + "='" + FieldTypes.META + "' />\n";
var attributes = {'type': 'hidden', 'value': metaValue};
attributes[SuplerAttributes.FIELD_TYPE] = FieldTypes.META;
attributes[SuplerAttributes.FIELD_NAME] = metaKey;
html += HtmlUtil.renderTag('input', attributes) + '\n';
});
return html + '</div>\n';
return html + '</span>\n';
} else {
return '';
}
@@ -102,8 +102,8 @@ class ReadFormValues {
private static appendMetaValue(result, fieldName, fieldValue) {
var meta;
if (!(meta = result[Sections.META])) {
result[Sections.META] = (meta = {});
if (!(meta = result[FormSections.META])) {
result[FormSections.META] = (meta = {});
}
meta[fieldName] = fieldValue;
@@ -67,7 +67,7 @@ class Bootstrap3RenderOptions implements RenderOptions {
}
renderHiddenField(fieldData, options, compact) {
return this.renderField(this.renderHtmlInput('hidden', fieldData.value, options), fieldData, compact);
return this.renderHiddenFormGroup(this.renderHtmlInput('hidden', fieldData.value, options));
}
renderTextareaField(fieldData, options, compact) {
@@ -123,6 +123,10 @@ class Bootstrap3RenderOptions implements RenderOptions {
return HtmlUtil.renderTag('div', {'class': 'form-group'}, divBody, false);
}
renderHiddenFormGroup(input) {
return HtmlUtil.renderTag('span', {'class': 'hidden-form-group', 'style': 'visibility: hidden; display: none'}, input, false);
}
renderLabel(forId, label) {
return HtmlUtil.renderTag('label', {'for': forId}, label);
}
@@ -38,7 +38,7 @@ class SuplerForm {
render(json) {
if (this.isSuplerForm(json)) { // might be custom-data-only result
var result = new CreateFormFromJson(this.renderOptionsGetter, this.i18n, this.validatorFnFactories)
.renderForm(json[Sections.META], json.main_form);
.renderForm(json[FormSections.META], json.main_form);
this.container.innerHTML = result.html;
this.initializeValidation(result.formElementDictionary, json);
@@ -22,6 +22,7 @@
<!-- Test data -->
<script src="generated/simple1.js"></script>
<script src="generated/simpleWithMeta.js"></script>
<script src="generated/actionSimple.js"></script>
<script src="generated/selectSingle.js"></script>
<script src="generated/complexSubformsList.js"></script>
@@ -11,6 +11,18 @@ describe('serialization', function() {
serialized.should.deep.equal(data.simple1.obj1);
});
it('should serialize a basic form with meta', function() {
// given
var sf = new SuplerForm(container);
sf.render(data.simpleWithMeta.form1);
// when
var serialized = sf.getValue();
// then
serialized.should.deep.equal(data.simpleWithMeta.obj1);
});
it('should serialize after changes', function() {
// given
var sf = new SuplerForm(container);
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -6,11 +6,11 @@ import org.supler.field._
import org.supler.validation._
case class Form[T](rows: List[Row[T]], createEmpty: () => T) {
def apply(obj: T): FormWithObject[T] = InitialFormWithObject(this, obj, None, Meta(Map()))
def apply(obj: T): FormWithObject[T] = InitialFormWithObject(this, obj, None, FormMeta(Map()))
def withNewEmpty: FormWithObject[T] = InitialFormWithObject(this, createEmpty(), None, Meta(Map()))
def withNewEmpty: FormWithObject[T] = InitialFormWithObject(this, createEmpty(), None, FormMeta(Map()))
def getMeta(jvalue: JValue): Meta = Meta.fromJSON(jvalue)
def getMeta(jvalue: JValue): FormMeta = FormMeta.fromJSON(jvalue)
private[supler] def doValidate(parentPath: FieldPath, obj: T, scope: ValidationScope): FieldErrors =
rows.flatMap(_.doValidate(parentPath, obj, scope))
@@ -0,0 +1,32 @@
package org.supler
import org.json4s.JValue
import org.json4s.JsonAST.{JField, JObject, JString}
case class FormMeta(meta: Map[String, String]) {
def apply(key: String): String = {
meta(key)
}
def +(key: String, value: String) = this.copy(meta = meta + (key -> value))
def toJSON = JField(FormMeta.JsonMetaKey, JObject(meta.toList.map {case (key, value) => JField(key, JString(value))}))
def isEmpty = meta.isEmpty
}
object FormMeta {
val JsonMetaKey = "supler_meta"
def fromJSON(json: JValue) = {
json match {
case JObject(fields) => fields.toMap.get(JsonMetaKey) match {
case Some(JObject(entries)) => FormMeta(entries.toMap.collect{case (key: String, value: JString) => key -> value.s})
case Some(_) => throw new IllegalArgumentException("Form meta is not well formed")
case None => FormMeta(Map())
}
case _ => FormMeta(Map())
}
}
}

This file was deleted.

Oops, something went wrong.
@@ -18,7 +18,7 @@ sealed trait SuplerData[+T] {
trait FormWithObject[T] extends SuplerData[T] {
def obj: T
def form: Form[T]
def meta: Meta
def meta: FormMeta
/**
* Custom data which will be included in the generated JSON, passed to the frontend.
* Not used or manipulated in any other way by Supler.
@@ -34,7 +34,7 @@ trait FormWithObject[T] extends SuplerData[T] {
def applyJSONValues(jvalue: JValue): FormWithObjectAndErrors[T] = {
val result = form.applyJSONValues(EmptyPath, obj, jvalue)
new FormWithObjectAndErrors(form, result.obj, customData, result.errors, Nil, Meta.fromJSON(jvalue))
new FormWithObjectAndErrors(form, result.obj, customData, result.errors, Nil, FormMeta.fromJSON(jvalue))
}
def doValidate(scope: ValidationScope = ValidateAll): FormWithObjectAndErrors[T] = {
@@ -81,20 +81,20 @@ trait FormWithObject[T] extends SuplerData[T] {
validated
} else {
runnableAction.run() match {
case FullCompleteActionResult(t, customData) => InitialFormWithObject(form, t.asInstanceOf[T], customData, Meta(Map()))
case FullCompleteActionResult(t, customData) => InitialFormWithObject(form, t.asInstanceOf[T], customData, meta)
case CustomDataCompleteActionResult(json) => CustomDataOnly(json)
}
}
}
}
}
case class InitialFormWithObject[T](form: Form[T], obj: T, customData: Option[JValue], meta: Meta)
case class InitialFormWithObject[T](form: Form[T], obj: T, customData: Option[JValue], meta: FormMeta)
extends FormWithObject[T] {
override protected val applyErrors = Nil
override protected val validationErrors = Nil
override def withMeta(key: String, value: String): InitialFormWithObject[T] = this.copy(meta = meta.addMeta(key, value))
override def withMeta(key: String, value: String): InitialFormWithObject[T] = this.copy(meta = meta + (key, value))
}
case class FormWithObjectAndErrors[T](
@@ -103,12 +103,12 @@ case class FormWithObjectAndErrors[T](
customData: Option[JValue],
applyErrors: FieldErrors,
validationErrors: FieldErrors,
meta: Meta) extends FormWithObject[T] {
meta: FormMeta) extends FormWithObject[T] {
def errors: FieldErrors = allErrors
def hasErrors: Boolean = allErrors.size > 0
override def withMeta(key: String, value: String): FormWithObjectAndErrors[T] = this.copy(meta = meta.addMeta(key, value))
override def withMeta(key: String, value: String): FormWithObjectAndErrors[T] = this.copy(meta = meta + (key, value))
}
case class CustomDataOnly private[supler] (customData: JValue) extends SuplerData[Nothing] {
@@ -4,10 +4,10 @@ import org.json4s.JsonAST.JObject
import org.json4s.native.JsonMethods._
import org.scalatest._
class MetaTest extends FlatSpec with ShouldMatchers {
class FormMetaTest extends FlatSpec with ShouldMatchers {
"meta" should "serialize to json" in {
// given
val m = Meta(Map()).addMeta("tomek", "domek").addMeta("witek", "sprytek")
val m = FormMeta(Map()) + ("tomek", "domek") + ("witek", "sprytek")
// when
val json = compact(render(JObject(m.toJSON)))
@@ -32,7 +32,7 @@ class MetaTest extends FlatSpec with ShouldMatchers {
val jsonParsed = parse(json)
// when
val meta = Meta.fromJSON(jsonParsed)
val meta = FormMeta.fromJSON(jsonParsed)
// then
meta("entityId") should be ("123")
Oops, something went wrong.

0 comments on commit 946df16

Please sign in to comment.