├── .central.synchro.sh ├── .check.supler.commited.sh ├── .gitignore ├── .travis.credentials.sh ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── build.sbt ├── design ├── ajax-design.md ├── attributes.md ├── js-design.md └── supler diagram.png ├── docs ├── .gitignore ├── Makefile ├── _templates │ └── page.html ├── autobuild.sh ├── backend │ ├── .DS_Store │ ├── formdef │ │ ├── actions.rst │ │ ├── basics.rst │ │ ├── conditional.rst │ │ ├── renderhints.rst │ │ ├── select.rst │ │ ├── static.rst │ │ ├── subforms.rst │ │ ├── typetransformations.rst │ │ └── validation.rst │ ├── formwithobject.rst │ └── meta.rst ├── conf.py ├── first.html ├── first.rst ├── frontend │ ├── clientsideval.rst │ ├── complexjson.rst │ ├── custombehavior.rst │ ├── customdata.rst │ ├── customizingrender.rst │ ├── i18n.rst │ ├── options.rst │ ├── refreshes.rst │ ├── rendering.rst │ └── serializing.rst ├── index.rst ├── json-example.json ├── json.rst ├── livedemo.rst ├── make.bat └── setup.rst ├── examples ├── Play2Example │ ├── .gitignore │ ├── HOWTO.md │ ├── LICENSE │ ├── README │ ├── activator │ ├── activator-launch-1.2.8.jar │ ├── activator.bat │ ├── app │ │ ├── controllers │ │ │ ├── APICrudController.scala │ │ │ └── Application.scala │ │ ├── core │ │ │ ├── CoreObjects.scala │ │ │ └── types │ │ │ │ └── Types.scala │ │ ├── forms │ │ │ └── PersonForm.scala │ │ ├── helpers │ │ │ └── JsonImplicits.scala │ │ ├── models │ │ │ └── Person.scala │ │ └── views │ │ │ ├── index.scala.html │ │ │ └── main.scala.html │ ├── build.sbt │ ├── conf │ │ ├── application.conf │ │ ├── logback.xml │ │ └── routes │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ ├── public │ │ ├── css │ │ │ ├── slate.bootstrap.css │ │ │ ├── slate.bootstrap.min.css │ │ │ └── slate.bootswatch.min.css │ │ ├── images │ │ │ └── favicon.png │ │ ├── javascripts │ │ │ └── hello.js │ │ ├── js │ │ │ └── forms.js │ │ └── stylesheets │ │ │ └── main.css │ └── test │ │ └── ApplicationSpec.scala └── src │ └── main │ ├── resources │ ├── forms.js │ ├── index.html │ └── lib │ │ ├── bootstrap-datepicker.min.js │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.js │ │ ├── datepicker3.standalone.min.css │ │ ├── html5shiv.min.js │ │ ├── jquery.min.js │ │ └── respond.min.js │ └── scala │ └── org │ └── demo │ ├── DemoServer.scala │ ├── DocsPersonForm.scala │ ├── PersonForm.scala │ └── RegisterDataForm.scala ├── project ├── build.properties └── plugins.sbt ├── supler-js ├── .editorconfig ├── .gitignore ├── Gruntfile.js ├── package.json ├── src │ ├── Constants.ts │ ├── CreateFormFromJson.ts │ ├── ElementSearch.ts │ ├── FieldData.ts │ ├── FieldMatcher.ts │ ├── FieldOptions.ts │ ├── FormElement.ts │ ├── HtmlUtil.ts │ ├── I18n.ts │ ├── Log.ts │ ├── ReadFormValues.ts │ ├── RenderOptions.ts │ ├── SendController.ts │ ├── SuplerForm.ts │ ├── Util.ts │ ├── customizerender │ │ ├── HtmlRenderTemplateParser.ts │ │ ├── RenderModifiersFromFieldOptions.ts │ │ ├── RenderOptionsGetter.ts │ │ ├── RenderOptionsModifier.ts │ │ └── SingleTemplateParser.ts │ └── validation │ │ ├── ElementValidator.ts │ │ ├── Validation.ts │ │ ├── ValidationScope.ts │ │ ├── ValidatorFn.ts │ │ └── ValidatorRenderOptions.ts └── tests │ ├── .gitignore │ ├── common.js │ ├── complexFieldTest.js │ ├── conditionalTests.js │ ├── customDataTests.js │ ├── customizeRenderTests.js │ ├── fieldMatcherTests.js │ ├── fieldOrderTests.js │ ├── formValidationTests.js │ ├── frontendRenderHintTests.js │ ├── runner.html │ ├── selectSingleTests.js │ ├── sendTests.js │ └── serializeTests.js ├── supler.js ├── supler.min.js └── supler └── src ├── main └── scala │ └── org │ └── supler │ ├── FieldPath.scala │ ├── Form.scala │ ├── FormMeta.scala │ ├── Message.scala │ ├── Row.scala │ ├── Supler.scala │ ├── SuplerData.scala │ ├── SuplerFieldMacros.scala │ ├── SuplerFormMacros.scala │ ├── Util.scala │ ├── field │ ├── ActionField.scala │ ├── BasicField.scala │ ├── Field.scala │ ├── GenerateBasicJSON.scala │ ├── RenderHint.scala │ ├── SelectField.scala │ ├── SelectManyField.scala │ ├── SelectOneField.scala │ ├── StaticField.scala │ ├── SubformField.scala │ ├── ValidateWithValidators.scala │ ├── fieldCompatibility.scala │ └── package.scala │ ├── package.scala │ ├── transformation │ ├── BasicTypeTransformer.scala │ ├── JsonTransformer.scala │ └── Transformer.scala │ └── validation │ ├── FieldErrorMessage.scala │ ├── PartiallyAppliedObj.scala │ ├── ValidationScope.scala │ ├── Validator.scala │ └── package.scala └── test └── scala └── org └── supler ├── ApplyTest.scala ├── ConditionalFieldsTest.scala ├── FieldOrderTest.scala ├── FieldPathTest.scala ├── FieldUniquenessTest.scala ├── FormMetaTest.scala ├── FrontendTestsForms.scala ├── ProcessTest.scala ├── RecursiveTest.scala ├── SelectFieldTest.scala ├── SuplerActionTest.scala ├── SuplerSubformTest.scala ├── SuplerTest.scala ├── SuplerValidationTest.scala └── TransformerTest.scala /.central.synchro.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ ${PROJECT_VERSION} != *-SNAPSHOT ]] 3 | then 4 | curl -vvf -u$BINTRAY_USER:$BINTRAY_PASSWORD -H "Content-Type: application/json" \ 5 | -X POST https://bintray.com/api/v1/maven_central_sync/softwaremill/softwaremill/supler/versions/${PROJECT_VERSION} \ 6 | --data "{ \"username\": \"${SONATYPE_USER}\", \"password\": \"${SONATYPE_PASSWORD}\", \"close\": \"1\" }" 7 | echo "Release synchronized to central" 8 | else 9 | echo "Ignoring snapshot version" 10 | fi -------------------------------------------------------------------------------- /.check.supler.commited.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ `git status --porcelain supler.js` != "" ]] || [[ `git status --porcelain supler.min.js` != "" ]] 3 | then 4 | exit 100 5 | else 6 | exit 0 7 | fi -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache/ 6 | .history/ 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | 19 | #idea 20 | .idea 21 | 22 | **/*.iml 23 | *.iml 24 | .idea_modules 25 | 26 | **/node_modules 27 | 28 | .DS_Store 29 | 30 | .run.central.synchro.sh -------------------------------------------------------------------------------- /.travis.credentials.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir ~/.bintray/ 3 | BINTRAY_FILE=$HOME/.bintray/.credentials 4 | ARTIFACTORY_FILE=$HOME/.bintray/.artifactory 5 | cat <$BINTRAY_FILE 6 | realm = Bintray API Realm 7 | host = api.bintray.com 8 | user = $BINTRAY_USER 9 | password = $BINTRAY_PASSWORD 10 | EOF 11 | 12 | cat <$ARTIFACTORY_FILE 13 | realm = Artifactory Realm 14 | host = oss.jfrog.org 15 | user = $BINTRAY_USER 16 | password = $BINTRAY_PASSWORD 17 | EOF -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | sudo: false 3 | scala: 4 | - 2.11.5 5 | script: 6 | - sbt ++$TRAVIS_SCALA_VERSION test 7 | - /bin/bash .check.supler.commited.sh 8 | after_success: 9 | - '[ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ] && /bin/bash "$TRAVIS_BUILD_DIR/.travis.credentials.sh" && sbt publish && sbt makeVersionSh && /bin/bash .run.central.synchro.sh' 10 | env: 11 | global: 12 | - secure: rI0yiqoisNVKhH/ofJ00Ymj47Uw4FYvxhiNmCv5LObfu6dQQohaMi9BAVzpr+aVvBeXfWT5As9giTuDZfcxkHoXXMM0VIEgp5XpLxRFDOJBmvgagJqN/tM22yU3aDexQm/ORBpETLnYtvbazbo6q7fYT+pfc8HEtu8BfbpLe4ro= 13 | - secure: f2iy9eo7ZQThWGNW1kCllTEFaGi9EuiJKYnRe6SsbXZjmxeo8Q+KvPvbqOjkvdH43krFySnrF1VO/fdhDMcP2XmnCkyyX37K/c/iOZSWoyCydYeydM0YgNoUpoxiFC9WCgffSWI4q1Tn0RsC+yzXrysXf6/ii4S8SBuRv4yBTHA= 14 | - secure: f3IJrafmfHgCY5HNep4/jO6K0KBpc4wkKm4yG3sK/lzcRS0oD9t1/rY1YbO7msUcIp4YcLOVJvBIKIwfNSI5yA+5RTogeW1LllE5WawvxsRPfeH1MJneb/eTpRzBjAVyK/9u3l9KzuPt+GDJuILK2Sc7XaihKj5mhXmQ5mI2aoI= 15 | - secure: FTcC++wfkERq/HWopJQyG6BctvhUUw2EySyshksN3B5aVekztT+fx55OFM0BmZ75EyGhq4r0SFJvirCcmq9+uUwQse++OqZbNEGw/71FB55FD1Wzo5Xg+XaO6FS2YU+J58dPWhBTHCurP+ETqxFz/oPIOAPbHSIvGsvESlLshrA= 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # supler - Functional Reactive Form Library 2 | 3 | [![Build Status](https://travis-ci.org/softwaremill/supler.svg?branch=master)](https://travis-ci.org/softwaremill/supler) 4 | [![Join the chat at https://gitter.im/softwaremill/supler](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/softwaremill/supler?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.supler/supler_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.supler/supler_2.11) 6 | 7 | ## Links 8 | 9 | * [documentation](http://docs.supler.io) 10 | * [forum](https://groups.google.com/forum/#!forum/supler) 11 | * [the introduction blog](http://www.warski.org/blog/2014/09/introducing-supler-a-functional-reactive-form-library/) 12 | * [the live demo](http://supler.softwaremill.com/) 13 | * live-coding conference talks: [shorter (30 mins) from Scalar](https://www.youtube.com/watch?v=ex9H_pHdFZ4&list=PL8NC5lCgGs6N5_mHAx9LjOO1NBEADQ4cP&index=1&spfreload=10), [longer (60 mins) from LJC](https://skillsmatter.com/skillscasts/6342-supler-complex-web-forms-not-so-complex#video) 14 | 15 | ## Introduction 16 | 17 | Supler is a **library** which makes writing complex form easier. It has server-side (Scala) and client-side 18 | (JavaScript) components. 19 | 20 | On the server side Supler provides: 21 | 22 | * a DSL for defining forms 23 | * a way to generate a JSON description of a form 24 | * running server-side conversion and validation 25 | * running server-side actions 26 | * applying values sent from the frontend to the backing object 27 | 28 | On the frontend side Supler provides: 29 | 30 | * generating HTML basing on JSON form description 31 | * serializing a form to JSON 32 | * running client-side validations 33 | * customizability of the HTML generation process 34 | * automatically refresh the form with server-side changes after a field is edited 35 | 36 | Supler does not define or mandate how the objects/entities backing the forms should work, how are they persisted, 37 | how are sessions managed or how you handle requests. It is also agnostic to other JS frameworks and libraries. The 38 | generated HTML has elements with predictable names, which can be easily customized. 39 | 40 | ## Supler diagram 41 | 42 | ![Supler diagram](https://raw.githubusercontent.com/softwaremill/supler/master/design/supler%20diagram.png) 43 | 44 | ## Hacking on Supler 45 | 46 | The backend is built using [SBT](http://www.scala-sbt.org). 47 | 48 | The frontend is built using [Grunt](http://gruntjs.com). To start on-change compilation of Typescript sources, 49 | running tests and a live-reload server: 50 | 51 | * `cd supler-js` 52 | * `npm install` 53 | * `grunt dev` 54 | 55 | ## Version history 56 | 57 | 0.3.0 - 18/03/2015 58 | 59 | * field options on the frontend: 60 | - field order 61 | - javascript rendering overrides 62 | - render hints overrides 63 | * custom render hints 64 | * multiple fields in one row rendering 65 | * complex fields support 66 | * bug fixes and various improvements 67 | 68 | 0.2.0 - 29/01/2015 69 | 70 | * subforms extensions 71 | * ajax queueing 72 | * tests 73 | * bug fixes 74 | * docs 75 | 76 | 0.1.0 - 16/12/2014 77 | 78 | * initial release 79 | 80 | ## Contributors 81 | 82 | * [Tomasz Szymański](http://twitter.com/szimano) 83 | * [Adam Warski](http://twitter.com/adamwarski) 84 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supler", 3 | "version": "0.3.0", 4 | "main": "supler.js", 5 | "keywords": [ "forms", "power user", "scala", "supler" ], 6 | "ignore": [ 7 | ".idea", 8 | ".travis.yml", 9 | ".gitignore", 10 | "design", 11 | "docs", 12 | "examples", 13 | "project", 14 | "src", 15 | "supler", 16 | "supler-js", 17 | "target", 18 | "*.sh" 19 | ], 20 | "dependencies": { 21 | 22 | }, 23 | "devDependencies": { 24 | "mocha": "1.21.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /design/ajax-design.md: -------------------------------------------------------------------------------- 1 | On action validate: nothing, field, subform, form; if not valid -> no action (b/e) 2 | 3 | Ajax actions: button, field change (grace? f/e and b/e?): either during change or after 4 | 5 | Per-field JS settings (e.g. ajax behavior, default ajax behavior) 6 | 7 | How to enable specifying text field width/height on the f/e? 8 | 9 | Ajax update on any change (blur) - configurable per-field (path)? e.g. refresh_onblur_a_b_c -------------------------------------------------------------------------------- /design/attributes.md: -------------------------------------------------------------------------------- 1 | Supler attribute requirements 2 | ============================= 3 | 4 | Each field carries a number of meta-data elements allowing Supler to do its job. 5 | In the most basic setting, the attributes are placed on the input element. The situation is a bit more complicated 6 | when a single field translates to a number of input elements (checkboxes/radio buttons), or in the presence of subforms. 7 | 8 | Regular field 9 | ------------- 10 | 11 | * `supler:fieldName`: name of the field. Same as the `name` input attribute 12 | * `supler:fieldType`: supler-type of the field. Used e.g. during serialization 13 | * `supler:multiple`: if this field can take multiple values. For regular single-value fields this will be `false`. 14 | * `supler:validationId`: id of the element where validation messages can be added 15 | * `supler:path`: path to the element. Used e.g. during reloads and when applying validations after reloads 16 | 17 | Checkable fields 18 | ---------------- 19 | 20 | Radio buttons/checkboxes. These need to be rendered with a containing element, which has some of the attributes. 21 | 22 | Individual input: as above except `supler:path`. 23 | 24 | Containing element: 25 | 26 | * `supler:validationId`: there is a single validation element for all contained inputs 27 | * `supler:path`: when looking for the element by-path, the containing element should be found 28 | 29 | Note that the validation id needs to be present both on the individual and containing elements, so that the validation 30 | element can be looked up both when triggered by a single input change, and when triggered by e.g. global validation. 31 | 32 | Subforms 33 | -------- 34 | 35 | Each element containing a subform: 36 | 37 | * `supler:fieldName` 38 | * `supler:fieldType` 39 | * `supler:multiple`: if there are multiple subforms, this will be `true` 40 | 41 | Other attributes don't have to be used. -------------------------------------------------------------------------------- /design/js-design.md: -------------------------------------------------------------------------------- 1 | Description of a form in JSON 2 | ============================= 3 | 4 | Basing on that description, a form is rendered 5 | 6 | ```json 7 | { 8 | "fields": { 9 | "field1": { 10 | "label": "...", 11 | "type": "text" / "integer" / "double" / "boolean", 12 | // optional fields 13 | "validate": { 14 | "required": true, 15 | "minLength": 10, 16 | "maxLength": { 17 | "value": 20, 18 | "msg": "xxx" 19 | } 20 | }, 21 | "multiple": false 22 | "render_hint": "textarea" / "radio" / "checkbox" / "slider" / etc., 23 | "placeholder": "...", 24 | "possible_values": [ "...", "...", "..." ], 25 | "value": ... // an array if multiple 26 | }, 27 | "field2": { 28 | "label": "...", 29 | "type": "subform", 30 | "multiple": true 31 | "value": [ 32 | { 33 | // recursive 34 | }, ... 35 | ] // or a single form if not multiple 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | HTML 42 | ==== 43 | 44 | Basic form workflow 45 | ------------------- 46 | 47 | ````html 48 |
49 |
50 | ```` 51 | 52 | ````javascript 53 | // show 54 | var form = new Supler.Form( 55 | document.getElementById("form-container"), 56 | { 57 | // options - see below 58 | } 59 | }); 60 | form.render(formJson); // read from the server 61 | 62 | // serialize & send 63 | form.toJson(); 64 | 65 | // parse the response 66 | var suplerResponse = new SuplerResponse(responseJson); 67 | suplerResponse.isSubmitted(); // true or false; if false - server-side valdiation errors 68 | 69 | form.applyResponse(suplerResponse); // modify form, display validations 70 | ```` 71 | 72 | Other features 73 | -------------- 74 | 75 | **Predictable naming**: form elements are named after fields. It should be easy to add custom JS handlers to form 76 | elements. 77 | 78 | Notes 79 | ----- 80 | 81 | **Renderer types:** 82 | * text 83 | * number 84 | * password 85 | * textarea 86 | 87 | multi-choice 88 | * checkbox 89 | * select-multi 90 | 91 | single-choice 92 | * select-single 93 | * radio 94 | 95 | **HTML<->form element mapping** 96 | Unless otherwise stated, listed below are types. 97 | 98 | **Types**: 99 | text 100 | number 101 | file 102 | 103 | (not supported for now) 104 | color 105 | week 106 | time 107 | date 108 | datetime 109 | datetime-local 110 | month 111 | email 112 | url 113 | 114 | **Render hints**: 115 | password 116 | checkbox (for multi-select) 117 | radio 118 | range 119 |