/formtastic-js

Formtastic, but for JavaScript

Primary LanguageCoffeeScriptMIT LicenseMIT

Under Development

While there is a large test suite at the moment, there are missing inputs still.

Formtastic JS Port

Formtastic JS is a JS FormBuilder DSL that aims to duplicate the API given by the Rails Gem Formtastic. The goal of this project is to simply duplicate assumptions from the Rails Gem into the JavaScript project. Please see examples and tests for better coverage details.

Installation

bower install formtastic-js

Production Dependency

  • underscore.js
    • TODO: Remove dependency if possible. Using _.template, _.extend, _.clone, _.is***, _.first, _.last, _.compact

Usage

Examples in dir contain a normal Formtastic version along with a Bootstrap 3 version.

JavaScript

(function(root) {
  var model = {};
  var form = new Formtastic(model, {as: 'blog', url: '/some/path'}, function(f) {
    var c = [];
    c.push(
      f.inputs({name: 'Basic'}, function(b) {
        var bf = [];
        bf.push(b.input('title'));
        bf.push(b.input('body', {as: 'text'}));
        bf.push(b.input('section'));

        // bf.push(b.input('publication_state', {as: 'radio'}));
        bf.push(b.input('category'));
        bf.push(b.input('colors', {as: 'select', collection: [{value: '1', text: 'Blue'}, {value: '2', text: 'Green'}, {value: '3', text: 'Red'}]}));
        bf.push(b.input('more_colors', {as: 'select', collection: [['Blue', 1], ['Green', 2], ['Red', 3]]}));
        bf.push(b.input('allow_comments', {as: 'checkbox', label: "Allow commenting on this article"}));
        bf.push(b.input('body', {as: 'text'}));

        bf.push(b.inputs('Private', {for: 'pm'}, function(b2) {
          var b = [];
          b.push(b2.input('private_category'));
          b.push(b2.input('private_colors', {as: 'select', collection: [{value: '1', text: 'Blue'}, {value: '2', text: 'Green'}, {value: '3', text: 'Red'}]}));
          return b.join("\n");
        }));
        return bf.join("\n");
      })
    );

    c.push(
      f.inputs("Advanced", function(b) {
        var bf = [];
        bf.push(b.input('keywords', {required: false, hint: "Example: ruby, rails, forms"}));
        bf.push(b.input('password', {required: false, input_html: {value: 'hello'}}));
        bf.push(b.input('password_confirmation', {required: false, input_html: {value: 'hello'}}));
        bf.push(b.input('url_title', {required: false}));
        return bf.join("\n");
      })
    );

    c.push(
      f.inputs({name: "Author", for: 'author'}, function(b) {
        var bf = [];
        bf.push(b.input('hidden', {as: 'hidden'}));
        bf.push(b.input('search'));
        return bf.join("\n");
      })
    );

    c.push(
      f.actions(function(b) {
        var bf = [];
        bf.push(b.action('submit', {as: 'button'}));
        bf.push(b.action('cancel', {as: 'link'}));
        return bf.join("\n");
      })
    );

    return c.join("\n");
  });
  html = form.render();
  root.document.getElementById('form').innerHTML = html;
})(this);

HAML Coffee

window.semantic_form_for = (object, form_attributes, fn)->
  f = new Formtastic(object, form_attributes, fn)
  return f.render()

=semantic_form_for this, {action: '#', class: 'form form-horizontal'}, (f)->
  =f.inputs "Some field set", (f2)->
    =f2.input('name', {required: true, label: "Name"})
    =f2.input('slug', {required: true, label: "Slug", hint: "Please parameterize your string, or we'll do it for you"})
    =f2.input('featured', {as: 'boolean', label: "Featured?"})

The Available Inputs

  • UNTESTED: {as: 'select'} - a select menu. requires collection to be passed
  • TODO: {as: 'check_boxes'} - a set of check_box inputs. Alternative to select
  • TODO: {as: 'radio'}- a set of radio inputs. Alternative to select
  • TODO: {as: 'time_zone'} - a select input. Default for column types: name matching "time_zone".
  • {as: 'password'}- a password input. Default for column types: string with name matching "password".
  • {as: 'text'} - a textarea.
  • TODO: {as: 'date_select'} - a date select.
  • TODO: {as: 'datetime_select'} - a date and time select.
  • TODO: {as: 'time_select'} - a time select. Default for column types: @:time@.
  • {as: 'boolean|checkbox|bool|check_box'} - a checkbox.
  • {as: 'string'} - a text field. Default type for all inputs.
  • {as: 'number'} - a text field (just like string).
  • {as: 'file'} - a file field.
  • TODO: {as: 'country'} - a select menu of country names. Default for column types: name "country"
  • {as: 'email'} - a text field (just like string). Default for columns with name matching "email". New in HTML5. Works on some mobile browsers already.
  • {as: 'url'} - a text field (just like string). Default for columns with name matching "url". New in HTML5. Works on some mobile browsers already.
  • {as: 'phone'} - a text field (just like string). Default for columns with name matching "phone" or "fax". New in HTML5.
  • {as: 'search'} - a text field (just like string). Default for columns with name matching "search". New in HTML5. Works on Safari.
  • {as: 'hidden'} - a hidden field. Creates a hidden field (added for compatibility).

Form Helpers

Those really helpful form_for helpers are also available along side the input method.

// indicates it has been implemented

=semantic_form_for({}, {as: 'dummy'}, function(f) {
  var c = [
    f.check_box :field, //
    f.color_field :field,
    f.date_field :field,
    f.datetime_field :field,
    f.datetime_local_field :field,
    f.email_field :field, //
    f.fields_for :field,
    f.file_field :field, //
    f.form_for :field,
    f.hidden_field :field, //
    f.label :field, //
    f.month_field :field,
    f.number_field :field, //
    f.password_field :field, //
    f.phone_field :field, //
    f.radio_button :field,
    f.range_field :field,
    f.search_field :field, //
    f.telephone_field :field, //
    f.text_area :field, //
    f.text_field :field, //
    f.time_field :field,
    f.url_field :field, //
    f.week_field :field,
    f.submit(), //
    f.cancel(), //
    f.reset(), //
    f.action('submit', label: 'Create Dummy') //
    f.action('cancel', label: 'Create Dummy') //
    f.action('reset', label: 'Create Dummy') //
  ];
  return c.join("\n");
});

Templating

By default follows the original Formtastic Rails Gem. However, alot of people use the forked version, formtastic-bootstrap3. This JS Port uses Underscore's Templating system _.template() to allow custom templates.

Default

Formtastic.template = '<%= label %><%= input %><%= hint %>';
Formtastic.default_form_class = 'formtastic';
Formtastic.default_wrapper_tag = 'li';
Formtastic.default_wrapper_class = '';
Formtastic.default_label_class = 'label';
Formtastic.default_input_class = '';
Formtastic.default_fieldset_inner_tag = 'ol';
Formtastic.required_string = '*';
Formtastic.default_hint_class = 'inline-hints';
Formtastic.default_hint_tag = 'p';

Bootstrap 3

Formtastic.template = '<%= label %>' +
                      '<div class="col-xs-10<% if (!label) { %> col-xs-offset-2<% } %>">' +
                        '<%= input %>' +
                        '<%= hint %>' +
                      '</div>';
Formtastic.default_form_class = 'form-horizontal';
Formtastic.default_wrapper_tag = 'div';
Formtastic.default_wrapper_class = 'form-group';
Formtastic.default_label_class = 'col-xs-2 control-label';
Formtastic.default_input_class = 'form-control';
Formtastic.default_fieldset_inner_tag = 'div';
Formtastic.required_string = ' <strong data-toggle="tooltip" data-title="Required" class="required text-danger">*</strong>';
Formtastic.default_hint_class = 'help-block';
Formtastic.default_hint_tag = 'p';

Formtastic Differences (JS vs Rails Gem)

  • Inputs that pass for options will only do singular
    f.inputs({for: 'author'}, function(f) {f.input('login')}) === f.inputs({for: 'authors'}, function(e) {f.input('login')})
    //= <fieldset for="author" class="inputs"><ol><li class="string optional"><input name="blog[author][login]" type="text"></li></ol></fieldset>
    
  • Inputs that just render themselves are empty fieldsets. The Rails version does DB Column look up
    f.inputs() //=> <fieldset class="inputs"></fieldset>
    
  • Numbers Input Field
    # Ruby
    builder.input(:title, :as => :number, :input_html => { :in => 5..102 })
    # JS
    builder.input('title', as: 'number', input_html: { min: 5, max: 102 })
    
  • Namespace concept does not exist
    # Ruby
    f.inputs(namespace: 'world') do |f2|
      f2.input(:title)
    end
    # JS n/a
    
  • JavaScript does not currently set the value automatically
  • There is a notation to remove f.action from Formtastic. Not going into too much depth for this so skipping the wrapper part. The buttons will still display perfectly. does not exist in JavaScript
    f.action(:submit, :wrapper_html => {:class => [ :my_class, :another_class ]})
    

Development

npm install
bower install

Run test suite (uses FireFox browser):

./node_modules/karma/bin/karma start

Contrib

Want to contribute to this project?

  • Create feature branch prefixed by feature name
  • Write Specs for Feature
  • Update CHANGELOG.md to reflect fixes, additions, etc.
  • Commit changes to branch and push to remote
  • Submit Pull Request