Code Monkey home page Code Monkey logo

ampersand-form-view's Introduction

ampersand-form-view

Lead Maintainer: Michael Garvin

overview

ampersand-form-view is a wrapper view for easily building complex forms on the clientside with awesome clientside validation and UX.

It would work quite well with backbone apps or anything else really, it has no external dependencies.

At a high level, the way it works is you define a view object (by making an object that following the simple view conventions of ampersand).

That form can be given an array of field views.

These fields are also views but just follow a few more conventions in order to be able to work with our form view.

Those rules are as follows:

  • It maintains a value property that is the current value of the field.
  • It should also store a value property if passed in as part of the config/options object when the view is created.
  • It maintains a valid property that is a boolean. The parent form checks this to know whether it can submit the form or not.
  • It has a name property that is a string of the name of the field.
  • It reports changes to its parent when it deems appropriate by calling this.parent.update(this) **note that it passes itsef to the parent. You would typically do this when the this.value has changed or the this.valid has changed.
  • When rendered by a form-view, the form view creates a parent property that is a reference to the containing form view.
  • It can optionally also define a beforeSubmit method. This gets called by the parent if it exists. This can be useful for stuff like a required text input that you don't want to show an error for if empty until the user tries to submit the form.

install

npm install ampersand-form-view

Example: Defining a form view

Here's how you might draw a form view as a subview.

// we'll just use an ampersand-view here as an
// example parent view
var View = require('ampersand-view');
var FormView = require('ampersand-form-view');
var InputView = require('ampersand-input-view');

var AwesomeFormView = View.extend({
    template: '<div><p>App form</p><form data-hook="app-edit-form"></form></div>',
    render: function () {
        this.renderWithTemplate();
        this.form = new FormView({
            autoRender: true,
            el: this.queryByHook('app-edit-form'),
            submitCallback: function (obj) {
                console.log('form submitted! Your data:', obj);
            },
            // this valid callback gets called (if it exists)
            // when the form first loads and any time the form
            // changes from valid to invalid or vice versa.
            // You might use this to disable the "submit" button
            // any time the form is invalid, for example.
            validCallback: function (valid) {
                if (valid) {
                    console.log('The form is valid!');
                } else {
                    console.log('The form is not valid!');
                }
            },
            // This is just an array of field views that follow
            // the rules described above. I'm using an input-view
            // here, but again, *this could be anything* you would
            // pass it whatever config items needed to instantiate
            // the field view you made.
            fields: [
                new InputView({
                    name: 'client_name',
                    label: 'App Name',
                    placeholder: 'My Awesome App',
                    // an initial value if it has one
                    value: 'hello',
                    // this one takes an array of tests
                    tests: [
                        function (val) {
                            if (val.length < 5) return "Must be 5+ characters.";
                        }
                    ]
                })
            ],
            // optional initial form values specified by
            // {"field-name": "value"} pairs. Overwrites default
            // `value`s provided in your FieldView constructors, only
            // after the form is rendered. You can set form values
            // in bulk after the form is rendered using setValues().
            values: {
                client_name: 'overrides "hello" from above'
            }
        });

        // registering the form view as a subview ensures that
        // its `remove` method will get called when the parent
        // view is removed.
        this.registerSubview(this.form);
    }
});

var awesomeFormView = new AwesomeFormView();
awesomeFormView.render();

FormView Options FormView.extend(options)

Standard view conventions apply, with the following options added:

  • autoRender : boolean (default: true)
    • Render the form immediately on construction.
  • autoAppend : boolean (default: true)
    • Adds new nodes for all fields defined in the fields array. Use autoAppend: false in conjuction with el: yourElement in order to use your own form layout.
  • fields : array
    • Array of FieldViews. If autoAppend is true, nodes defined by the view are built and appended to the end of the FormView.
  • submitCallback : function
    • Called on form submit
  • validCallback : function
    • This valid callback gets called (if it exists) when the form first loads and any time the form changes from valid to invalid or vice versa. You might use this to disable the "submit" button any time the form is invalid, for example.
  • clean : function
    • Lets you provide a function which will clean or modify what is returned by getData and passed to submitCallback.
  • fieldContainerEl : Element | string
    • Container to append fields to (when autoAppend is true).
    • This can either be a DOM element or a CSS selector string. If a string is passed, ampersand-view runs this.query('YOUR STRING') to try to find the element that should contain the fields. If you don't supply a fieldContainerEl, it will first try to find an element with the selector '[data-hook~=field-container]'. If no element for appending fields to is found, it will fallback to this.el.

=======

API Reference

setValue formView.setValue(name, value)

Sets the provided value on the field matching the provided name. Throws when invalid field name specified.

getValue formView.getValue(name)

Gets the value from the associated field matching the provided name. Throws when invalid field name specified.

setValues formView.setValues([values])

For each key corresponding to a field's name found in values, the corresponding value will be set onto the FieldView. Executes when the the formView is rendered.

myForm = new FormView({
    fields: function() {
        return [
            new CheckboxView({
                name: 'startsTrue',
                value: true
            }),
            new CheckboxView({
                name: 'startsFalse',
                value: false
            }),
        ];
    }
});
myForm.render();

// bulk update form values
myForm.setValues({
    startsTrue: true,  //=> no change
    startsFalse: true  //=> becomes true
});

reset formView.reset()

Calls reset on all fields in the form that have the method. Intended to be used to set form back to original state.

clear formView.clear()

Calls clear on all fields in the form that have the method. Intended to be used to clear out the contents of the form.

Properties

The following are FormView observables, thus emit "change" events:

  • valid - the valid state of the form
  • data - form field view values in { fieldName: value, fieldName2: value2 } format

Verbose forms

For verbose forms used to edit nested data, you can write field names as paths. Doing so, the data observable is nested according to the paths you specified so you can set or save this data to a state or collection more easily.

Example

A form with a persons first and last name and an array of phone numbers, each of which has fields for type and number:

var form = new FormView({
  fields: [
    new InputView({name: 'name.first', value: 'Michael'}),
    new InputView({name: 'name.last', value: 'Mustermann'}),
    new InputView({name: 'phone[0].type', value: 'home'}),
    new InputView({name: 'phone[0].number', value: '1234567'}),
    new InputView({name: 'phone[1].type', value: 'mobile'}),
    new InputView({name: 'phone[1].number', value: '7654321'})
  ]
});

console.log(form.data);
// {
//     name: {first: 'Michael', last: 'Mustermann'},
//     phone: [
//         {type: 'home', number: '1234567'},
//         {type: 'mobile', number: '7654321'}
//     ]
// }

Special Events

  • submit - triggered when a form is submitted. Returns the data of the form as the only argument

Changelog

changelog

  • 6.0.0 - Upgrade to &-view 9.x
  • 5.1.0 - Add submit and valid events
  • 5.0.0 - Extend ampersand-view to add state, adds setValues(), setValue(), & getValue(). Change to not render() during construction by default.
  • 4.0.0 - (skipped)
  • 3.0.0 - Initialize prior to render, and permit autoRender: false
  • 2.2.3 - Adding reset. Starting in on building API reference.

credits

Created by @HenrikJoreteg

license

MIT

ampersand-form-view's People

Contributors

achingbrain avatar ahoym avatar bear avatar blairanderson avatar cdaringe avatar chesles avatar dhritzkiv avatar fbaiodias avatar henrikjoreteg avatar imlucas avatar kamilogorek avatar klaemo avatar kylefarris avatar latentflip avatar legastero avatar lukekarrys avatar nickryall avatar pgilad avatar rickbutler avatar wraithgar avatar yallups avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

ampersand-form-view's Issues

Fields with brackets in name no longer work as expected in 5.2.0

I have fields with names like products[] and statuses[] and now, because of the introduction of lodash.set(), the names are being converted to products and statuses (without the brackets). My API is expected the bracketed names.

I'll see if I can create a PR that allows for what 5.2.0 and lodash.set bring to the table without breaking this expected functionality.

Document how to add a submit button

I feel like I must be missing something but I can't find any reference to adding a submit button to a form. There are references to the submit event, which I imagine would be called by hitting enter while an <input> is in focus, but that's the closest I see. The only thing I can think of is providing a template to the form view. Is that the only way?

EDIT: Looks like the submitCallback isn't triggered by hitting enter unless there's a submit button in the form somewhere

Form in a view (should be very simple =)

Oki, I'm new to Ampersand, but here goes;

I have this form:

var FormView = require('ampersand-form-view');
var InputView = require('ampersand-input-view');

EditForm = FormView.extend({
  initialize: function(options) {
    debug('initialize', this.el, this.autoRender, this.submitCallback)
  },

  fields: function () {
    debug('fields!')

    var fields = [
      new InputView({
        label: 'Name',
        name: 'name',
        value: utils.getDotted(this, 'model.name'),
        placeholder: 'Name',
        parent: this
      })
    ];

    debug('fields', fields)

    return fields;
  }
});

And I want to use it in a couple of views, the render-method exemplified here:

render: function () {
    this.renderWithTemplate();

    this.form = new EditForm({
      autoRender: true,
      el: this.query('.edit-form'),

      submitCallback: function (data) {
        debug('submit', data);
      }
    });

    this.registerSubview(this.form);
    return this
  }

-this does not work.

Embedding the form code in a non-extended ampersand-form does work:

render: function () {
    this.renderWithTemplate();

    this.form = new FormView({
      autoRender: true,
      el: this.query('.edit-form'),
      fields: [
        new InputView({
          label: 'Name',
          name: 'name',
          value: utils.getDotted(this, 'model.name'),
          placeholder: 'Name',
          parent: this
        })
      ],
      submitCallback: function (data) {
        debug('submit', data);
      }
    });

    this.registerSubview(this.form);
    return this
  }

What am I missing?

General `change` event

Feature request: support a general change event when a field changes, and not just change:field.

This would match ampersand-state and ampersand-views behaviours.

Currently I do this by:

view.on('all', function(eventType) {
    if (/^change/.test(eventType)) {
        //match all change events
    }
});

which is less than ideal.

It would be nice to simply do:

view.on('change', function() {
    match all change events
});

This issue is related to #8, #20, and #42.

extend() does not work on grandchildren

var FormView = require('ampersand-form-view');

var MyForm = FormView.extend({}); //This works

var MyForm2 = MyForm.extend({}); //undefined is not a function

New object on every `render()`?

I’m a little confused about the example in the README.md.

Inside the class’s render(), the code calls new FormView and this.registerSubview. To me, these seem like things that should be in initialize() instead.

Is there a reason that they are in render()? Will rendering break if that code is moved?

form-view and field-view rendered relation inconsistency

problem statement

Some formField views render() immediately on construction. This is undesired behavior.
If the FormView doesn't render immediately, some child fieldViews will still claim .rendered,
which is a false claim. Consequently those child-views may not display at form.render() as a FormView will think that they are already rendered. :(

potential solutions

  1. update each FieldFiew to not autorender. e.g. match &-view behavior
    1. a complimentary update to view-conventions would likely need to be required
      1. maybe a test for visibility or if a parentNode is set, which it shouldn't be, unless render has been called directly or if autoRender enabled
  2. update fieldViews to extend &-view, and support autoRender in initialize()
    1. this is a nice solution, as the fieldViews become extendable for user needs with all of the other goodies &-view offers

this was likely introduced by my update to support autoRender in &-form-view, but regardless, needs to be addressed.

Easy to to get list of invalid form fields

Ran into an issue recently with an invalid form field preventing the form from submitting. Right now an invalid class is added to an element, but it would be nice to have a method on the class to get a list of invalid form fields.

var invalidFields = formView.invalidFields();

Render happens before Initialize

Try this:

var AmpersandFormView = require('ampersand-form-view');

var TestForm = AmpersandFormView.extend({
    initialize: function(attr, options) {
        console.log('1');
    },
    render: function()
    {
        console.log('2');
    }
});

new TestForm();

The output is:

2
1

This doesn't seem right to me. Ampersand-view does it the other way.

I think the issue is lines 31-35 in the src. Three things happen:

  1. Render the form
  2. Render all the fields
  3. Initialize

I had to reorder them as 3, 2, 1 because:

  • I needed information passed from the constructor to build my field elements dynamically. I could not just declare the fields variable in the .extend call as shown in the examples.
  • The fields needed to be rendered by the time I get to my render call because I append another element (a submit button) and apply some style-hacks (set autocomplete to off for all fields in the form via jquery properties) that need this.el to be populated.

My render looks something like:

render: function()    {
    AmpersandFormView.prototype.render.apply(this, arguments); // Get all the default FormView stuff done and create Fields

    $( this.el ).find('input').prop('autocomplete', 'off'); // Turn off autocomplete

    this.button = $('<input type="submit">').appendTo( $( this.el ) ); // Add a submit button!
}

I also ended up just making step 2 part of the FormView render call. I don't see why the fields would try to render if the FormView itself hasn't tried to render.

Does this make sense?

Thanks,
Ben

Example of multi-field validation?

A use case: I have two, numeric text inputs. One cannot be greater than the other. How would I validate that using ampersand input/form/whatever and still have input errors/messages work without extra code?

If you do it at the input level using test methods (which only have local value available), you have to use external variables (self.model or something). If you do it at the form level it's the same issue where data available internally to the object isn't enough. If you do it at the model level (with validate()) then you have the issue if manually triggering an invalid state on inputs.

I really hope someone has a simple answer for this. I haven't found one yet.

Add a method to reset (clear) all inputs

After submitting a form, one often wants to clear the form of all _fieldViews values. Add a #reset method.

Just never don't bind it to a "Reset Form" button. Those are awful.

No `FormView.extend()`?

In the README.md example, the class simply extends the Ampersand’s normal View, and then recreates a new FormView on every render(). (I asked about the implications of this in Issue #24.)

Why not just extend FormView? Does it not work? Are there disadvantages?

I have been (intensely) learning Ampersand for the past several weeks, but I am a little confused about all this. The example is doing many things differently from what I have come to expect when using extend to create custom States/Models/Views.

Better Documentation or Examples

Having a bit of a hard time getting this working. Documentation in this repo and on ampersandjs.com aren't consistent. Also no example of triggering the form submission.
I would love to see a complete example :)

listenTo not a method?

I just noticed that listenTo, listenToOnce, and listenToAndRun are not methods of the form-view instance. Is this correct? Shouldn't it inherit it those from ampersand-view?

Valid event

It would be nice if the view emitted a valid event whenever it's valid property changed.
Right now, I have something like this:

FormView.extend({
    validCallback: function(valid) {
        this.trigger("valid", valid);
    },
})

However, it's a pain to add it to all my forms, and I fires every time (vs. just when it changes).

Derived property "data" should emit "change" events

As a developer, I want to create a form view and only listen to changes of its "data" property, so that I can conveniently handle data changes.

This is especially useful for forms that autosave on data change and forms with dynamic fields.

Currently the "data" property is a cached derived property that will never emit any events. The documentation states otherwise:

https://www.npmjs.com/package/ampersand-form-view#properties

The following are FormView observables, thus emit "change" events:

valid - the valid state of the form
data - form field view values in { fieldName: value, fieldName2: value2 } format

Possible solution: Extend the update method to also trigger a "change" event passing the "data" property.

Complete or submitted property

@wraithgar, I can extend my own formView to accommodate my needs, but thought this feature may be useful for others as well. How would you feel about a PR for adding a complete property to the FormView?

It would signify that the form is in a state where all fields have been validated and the form has been submitted (at least once). The case could exist where you leave your form on the page post-submit, and then additional changes are made. This state could feasibly invalidate the intent of complete in that the form would be complete, but still clearly inwork as it exposed to the user. However, this could simply be documented as an edge case.

What's driving this idea is that I'm making a form-manager/questionnaire view. I need to get state updates on many forms from a single source. Another idea is to perhaps add a mixin to support this feature.

complete, submitted, beenSubmitted, submitCount, completeCount -- all could imply the same thing

Use custom checkbox

Hi,

I've only found this way (setTimeout) to activate a custom checkbox once a FormView is rendered.
Is there a proper way to do so?

var ContactView = View.extend({
    autoRender: true,
    template: '<form></form>',
    render: function () {
        this.renderWithTemplate();
        this.form = new FormView({
            autoRender: true,
            fields: [
                new CheckboxView({ name: 'newsletter', label: 'Newsletter'})
            ],
        });
        this.registerSubview(this.form);

        //
        // here is the temporary fix
        //
        setTimeout(function () {
            $(this.query('.ui.checkbox')).checkbox();
        }.bind(this), 0);

        return this;
    }
});

documentation // `fields` property

Not a big deal, but I didn't see docs on the fields property. It looks like field views in this array are generated and placed within <label> tags

aggregate functions

If all the fields have a getValue and setValue then it would be possible to expose them at this module as getValue(field) and setValue(field, value)

A getValues and setValues that returned and accepted a hash would also be possible.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.