Skip to content
This repository has been archived by the owner on Jun 23, 2020. It is now read-only.

Commit

Permalink
codereview: meta on forms
Browse files Browse the repository at this point in the history
  • Loading branch information
szimano committed Feb 24, 2015
1 parent 5916829 commit 946df16
Show file tree
Hide file tree
Showing 19 changed files with 176 additions and 84 deletions.
3 changes: 1 addition & 2 deletions bower.json
Expand Up @@ -15,8 +15,7 @@
"supler", "supler",
"supler-js", "supler-js",
"target", "target",
"*.sh", "*.sh"
"README.md"
], ],
"dependencies": { "dependencies": {


Expand Down
2 changes: 1 addition & 1 deletion docs/backend/formdef/renderhints.rst
Expand Up @@ -18,5 +18,5 @@ For example, to render a text field as a password::
Supported render hints: Supported render hints:


* for subforms: ``asList()`` (default), ``asTable()`` * 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()`` * for single-select fields: ``asRadio()``
36 changes: 36 additions & 0 deletions docs/backend/meta.rst
@@ -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.
1 change: 1 addition & 0 deletions docs/index.rst
Expand Up @@ -50,6 +50,7 @@ Complete documentation
backend/formdef/static backend/formdef/static
backend/formdef/conditional backend/formdef/conditional
backend/formwithobject backend/formwithobject
backend/meta
frontend/rendering frontend/rendering
frontend/clientsideval frontend/clientsideval
frontend/serializing frontend/serializing
Expand Down
2 changes: 1 addition & 1 deletion supler-js/src/Constants.ts
Expand Up @@ -18,6 +18,6 @@ class SuplerAttributes {
static PATH = 'supler:path'; static PATH = 'supler:path';
} }


class Sections { class FormSections {
static META = 'supler_meta'; static META = 'supler_meta';
} }
11 changes: 7 additions & 4 deletions supler-js/src/CreateFormFromJson.ts
Expand Up @@ -21,12 +21,15 @@ class CreateFormFromJson {


private generateMeta(meta:any) { private generateMeta(meta:any) {
if (meta) { 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) => { Util.foreach(meta, (metaKey, metaValue) => {
html += "<input type='hidden' " + SuplerAttributes.FIELD_NAME + "='" + metaKey + "' value='" + metaValue + "' " + var attributes = {'type': 'hidden', 'value': metaValue};
SuplerAttributes.FIELD_TYPE + "='" + FieldTypes.META + "' />\n"; 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 { } else {
return ''; return '';
} }
Expand Down
4 changes: 2 additions & 2 deletions supler-js/src/ReadFormValues.ts
Expand Up @@ -102,8 +102,8 @@ class ReadFormValues {


private static appendMetaValue(result, fieldName, fieldValue) { private static appendMetaValue(result, fieldName, fieldValue) {
var meta; var meta;
if (!(meta = result[Sections.META])) { if (!(meta = result[FormSections.META])) {
result[Sections.META] = (meta = {}); result[FormSections.META] = (meta = {});
} }


meta[fieldName] = fieldValue; meta[fieldName] = fieldValue;
Expand Down
6 changes: 5 additions & 1 deletion supler-js/src/RenderOptions.ts
Expand Up @@ -67,7 +67,7 @@ class Bootstrap3RenderOptions implements RenderOptions {
} }


renderHiddenField(fieldData, options, compact) { 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) { renderTextareaField(fieldData, options, compact) {
Expand Down Expand Up @@ -123,6 +123,10 @@ class Bootstrap3RenderOptions implements RenderOptions {
return HtmlUtil.renderTag('div', {'class': 'form-group'}, divBody, false); 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) { renderLabel(forId, label) {
return HtmlUtil.renderTag('label', {'for': forId}, label); return HtmlUtil.renderTag('label', {'for': forId}, label);
} }
Expand Down
2 changes: 1 addition & 1 deletion supler-js/src/SuplerForm.ts
Expand Up @@ -38,7 +38,7 @@ class SuplerForm {
render(json) { render(json) {
if (this.isSuplerForm(json)) { // might be custom-data-only result if (this.isSuplerForm(json)) { // might be custom-data-only result
var result = new CreateFormFromJson(this.renderOptionsGetter, this.i18n, this.validatorFnFactories) 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.container.innerHTML = result.html;


this.initializeValidation(result.formElementDictionary, json); this.initializeValidation(result.formElementDictionary, json);
Expand Down
1 change: 1 addition & 0 deletions supler-js/tests/runner.html
Expand Up @@ -22,6 +22,7 @@


<!-- Test data --> <!-- Test data -->
<script src="generated/simple1.js"></script> <script src="generated/simple1.js"></script>
<script src="generated/simpleWithMeta.js"></script>
<script src="generated/actionSimple.js"></script> <script src="generated/actionSimple.js"></script>
<script src="generated/selectSingle.js"></script> <script src="generated/selectSingle.js"></script>
<script src="generated/complexSubformsList.js"></script> <script src="generated/complexSubformsList.js"></script>
Expand Down
12 changes: 12 additions & 0 deletions supler-js/tests/serializeTests.js
Expand Up @@ -11,6 +11,18 @@ describe('serialization', function() {
serialized.should.deep.equal(data.simple1.obj1); 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() { it('should serialize after changes', function() {
// given // given
var sf = new SuplerForm(container); var sf = new SuplerForm(container);
Expand Down
28 changes: 17 additions & 11 deletions supler.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion supler.min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions supler/src/main/scala/org/supler/Form.scala
Expand Up @@ -6,11 +6,11 @@ import org.supler.field._
import org.supler.validation._ import org.supler.validation._


case class Form[T](rows: List[Row[T]], createEmpty: () => T) { 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 = private[supler] def doValidate(parentPath: FieldPath, obj: T, scope: ValidationScope): FieldErrors =
rows.flatMap(_.doValidate(parentPath, obj, scope)) rows.flatMap(_.doValidate(parentPath, obj, scope))
Expand Down
32 changes: 32 additions & 0 deletions supler/src/main/scala/org/supler/FormMeta.scala
@@ -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())
}
}
}
28 changes: 0 additions & 28 deletions supler/src/main/scala/org/supler/Meta.scala

This file was deleted.

14 changes: 7 additions & 7 deletions supler/src/main/scala/org/supler/SuplerData.scala
Expand Up @@ -18,7 +18,7 @@ sealed trait SuplerData[+T] {
trait FormWithObject[T] extends SuplerData[T] { trait FormWithObject[T] extends SuplerData[T] {
def obj: T def obj: T
def form: Form[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. * Custom data which will be included in the generated JSON, passed to the frontend.
* Not used or manipulated in any other way by Supler. * Not used or manipulated in any other way by Supler.
Expand All @@ -34,7 +34,7 @@ trait FormWithObject[T] extends SuplerData[T] {
def applyJSONValues(jvalue: JValue): FormWithObjectAndErrors[T] = { def applyJSONValues(jvalue: JValue): FormWithObjectAndErrors[T] = {
val result = form.applyJSONValues(EmptyPath, obj, jvalue) 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] = { def doValidate(scope: ValidationScope = ValidateAll): FormWithObjectAndErrors[T] = {
Expand Down Expand Up @@ -81,20 +81,20 @@ trait FormWithObject[T] extends SuplerData[T] {
validated validated
} else { } else {
runnableAction.run() match { 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 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] { extends FormWithObject[T] {
override protected val applyErrors = Nil override protected val applyErrors = Nil
override protected val validationErrors = 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]( case class FormWithObjectAndErrors[T](
Expand All @@ -103,12 +103,12 @@ case class FormWithObjectAndErrors[T](
customData: Option[JValue], customData: Option[JValue],
applyErrors: FieldErrors, applyErrors: FieldErrors,
validationErrors: FieldErrors, validationErrors: FieldErrors,
meta: Meta) extends FormWithObject[T] { meta: FormMeta) extends FormWithObject[T] {


def errors: FieldErrors = allErrors def errors: FieldErrors = allErrors
def hasErrors: Boolean = allErrors.size > 0 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] { case class CustomDataOnly private[supler] (customData: JValue) extends SuplerData[Nothing] {
Expand Down
Expand Up @@ -4,10 +4,10 @@ import org.json4s.JsonAST.JObject
import org.json4s.native.JsonMethods._ import org.json4s.native.JsonMethods._
import org.scalatest._ import org.scalatest._


class MetaTest extends FlatSpec with ShouldMatchers { class FormMetaTest extends FlatSpec with ShouldMatchers {
"meta" should "serialize to json" in { "meta" should "serialize to json" in {
// given // given
val m = Meta(Map()).addMeta("tomek", "domek").addMeta("witek", "sprytek") val m = FormMeta(Map()) + ("tomek", "domek") + ("witek", "sprytek")


// when // when
val json = compact(render(JObject(m.toJSON))) val json = compact(render(JObject(m.toJSON)))
Expand All @@ -32,7 +32,7 @@ class MetaTest extends FlatSpec with ShouldMatchers {
val jsonParsed = parse(json) val jsonParsed = parse(json)


// when // when
val meta = Meta.fromJSON(jsonParsed) val meta = FormMeta.fromJSON(jsonParsed)


// then // then
meta("entityId") should be ("123") meta("entityId") should be ("123")
Expand Down

0 comments on commit 946df16

Please sign in to comment.