Skip to content

Bootstrap 3 and Ember Data With Server Side Validations

strukturedkaos edited this page Nov 8, 2014 · 14 revisions

Here are the steps currently required to get ember-easyForm working with Bootstrap 3 and Ember-Data (with server side validations). In this application I've used Ember-CLI with a Rails backend and the ActiveModelAdapter, so your situation might be different.

There are some issues getting it to work with Ember-Validation to have Client and Server side validations: https://github.com/dockyard/ember-validations/issues/122

Step 1 - The Initializers

I'm not sure if the pattern is the same for other build systems, but Ember-CLI has an initializers directory where I've added some "modifications" (aka hacks) to ember easyForm.

// app/initializers/easy-form.js
export default {
  name: 'easyForm',
  initialize: function(container) {

    Ember.EasyForm.Input.reopen({
      errorsChanged: function() {
        this.set('hasFocusedOut', true);
        this.showValidationError();
      },
      classNameBindings: ['wrapperConfig.inputClass', 'wrapperErrorClass'],
      didInsertElement: function() {
        this.addObserver('context.errors.' + this.property + '.@each', this, 'errorsChanged');
      },
      isCheckbox: function() {
        if (this.get('inputOptionsValues.as') === 'checkbox') {
          return true;
        }
        else {
          return false;
        }
      }.property('inputOptionsValues'),
      divWrapperClass: function() {
        if (this.get('inputOptionsValues.as') === 'checkbox') {
          return 'checkbox';
        }
        else {
          return '';
        }
      }.property('inputOptionsValues')
    });

    Ember.EasyForm.Error.reopen({
      errorText: function() {
        var message = this.get('errors.firstObject.message');
        if (message === undefined) {
          return this.get('errors.firstObject');
        } else {
          return message;
        }
      }.property('errors.firstObject').cacheable(),
      updateParentView: function() {
        var parentView = this.get('parentView');
        if(this.get('errors.length') > 0) {
          parentView.set('wrapperErrorClass', 'has-error');
        }else{
          parentView.set('wrapperErrorClass', false);
        }
      }.observes('errors.firstObject')
    });

    Ember.EasyForm.Submit.reopen({
      disabled: function() {
        return this.get('formForModel.disableSubmit');
      }.property('formForModel.disableSubmit')
    });

    //-- Bootstrap 3 Class Names --------------------------------------------
    //-- https://github.com/dockyard/ember-easyForm/issues/47
    Ember.TextSupport.reopen({
      classNames: ['form-control']
    });
    // And add the same classes to Select inputs
    Ember.Select.reopen({
      classNames: ['form-control']
    });

    Ember.EasyForm.Config.registerTemplate('form-fields/input', container.lookup('template:form-fields/input'));

    Ember.EasyForm.Config.registerWrapper('default', {
      inputTemplate: 'form-fields/input',

      labelClass: 'control-label',
      inputClass: 'form-group',
      buttonClass: 'btn btn-primary',
      fieldErrorClass: 'has-error',
      errorClass: 'help-block'
    });

  }
};

Step 2 - The Template

In the initializer, you'll notice that we are pointing to a custom template for the input class. This template will allow us to use less verbose markup while maintaining the conventions expected by bootstrap.

// app/templates/form-fields/input.hbs
{{#unless view.isCheckbox}}
  {{label-field propertyBinding="view.property" textBinding="view.label"}}
{{/unless}}
<div {{bind-attr class=view.divWrapperClass}}>
  {{#unless view.isCheckbox}}
    {{input-field propertyBinding='view.property' inputOptionsBinding='view.inputOptionsValues'}}
  {{/unless}}
  {{#if view.isCheckbox}}
    <label>
      {{input-field propertyBinding='view.property' inputOptionsBinding='view.inputOptionsValues'}}
      {{view.label}}
    </label>
  {{/if}}
  {{#if view.hint}}
    {{hint-field propertyBinding="view.property" textBinding="view.hint"}}
  {{/if}}
  {{#if view.showError}}
    {{error-field propertyBinding="view.property"}}
  {{/if}}
</div>

Step 3 - Your Template

Because we've altered the default form wrapper, we should be able to write standard forms.

<h3>New Thing</h3>

{{#form-for model}}

  {{input title}}

  {{input shortDescription}}

  {{input longDescription}}

  {{submit}}

{{/form-for}}