From 97076c07264f93c1f4873f280c8d84621a03e640 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sun, 10 May 2015 19:46:50 -0400 Subject: [PATCH 01/73] Added basic components --- .editorconfig | 1 + .ember-cli | 3 +- .npmignore | 1 + .travis.yml | 2 + Brocfile.js | 6 +- addon/components/error-field.js | 9 + addon/components/form-control.js | 66 ++ addon/components/hint-field.js | 9 + addon/components/input-field.js | 6 + addon/components/label-field.js | 9 + addon/helpers/to-words.js | 9 + addon/initializers/easy-form-extensions.js | 4 +- addon/services/easy-form.js | 11 + addon/templates/components/error-field.hbs | 1 + addon/templates/components/form-control.hbs | 1 + addon/templates/components/hint-field.hbs | 1 + addon/templates/components/input-field.hbs | 1 + addon/templates/components/label-field.hbs | 1 + app/components/error-field.js | 3 + app/components/form-control.js | 3 + app/components/hint-field.js | 3 + app/components/input-field.js | 3 + app/components/label-field.js | 3 + app/helpers/to-words.js | 3 + app/services/easy-form.js | 3 + bower.json | 14 +- index.js | 9 - package.json | 20 +- tests/dummy/app/app.js | 4 +- tests/unit/components/error-field-test.js | 21 + tests/unit/components/form-control-test.js | 21 + tests/unit/components/hint-field-test.js | 21 + tests/unit/components/input-field-test.js | 21 + tests/unit/components/label-field-test.js | 21 + tests/unit/helpers/to-words-test.js | 12 + tests/unit/services/easy-form-test.js | 15 + vendor/ember-easy-form.js | 644 -------------------- 37 files changed, 307 insertions(+), 678 deletions(-) create mode 100644 addon/components/error-field.js create mode 100644 addon/components/form-control.js create mode 100644 addon/components/hint-field.js create mode 100644 addon/components/input-field.js create mode 100644 addon/components/label-field.js create mode 100644 addon/helpers/to-words.js create mode 100644 addon/services/easy-form.js create mode 100644 addon/templates/components/error-field.hbs create mode 100644 addon/templates/components/form-control.hbs create mode 100644 addon/templates/components/hint-field.hbs create mode 100644 addon/templates/components/input-field.hbs create mode 100644 addon/templates/components/label-field.hbs create mode 100644 app/components/error-field.js create mode 100644 app/components/form-control.js create mode 100644 app/components/hint-field.js create mode 100644 app/components/input-field.js create mode 100644 app/components/label-field.js create mode 100644 app/helpers/to-words.js create mode 100644 app/services/easy-form.js create mode 100644 tests/unit/components/error-field-test.js create mode 100644 tests/unit/components/form-control-test.js create mode 100644 tests/unit/components/hint-field-test.js create mode 100644 tests/unit/components/input-field-test.js create mode 100644 tests/unit/components/label-field-test.js create mode 100644 tests/unit/helpers/to-words-test.js create mode 100644 tests/unit/services/easy-form-test.js delete mode 100644 vendor/ember-easy-form.js diff --git a/.editorconfig b/.editorconfig index 2fe4874..47c5438 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,6 +18,7 @@ indent_style = space indent_size = 2 [*.hbs] +insert_final_newline = false indent_style = space indent_size = 2 diff --git a/.ember-cli b/.ember-cli index 26cfcf3..ee64cfe 100644 --- a/.ember-cli +++ b/.ember-cli @@ -5,6 +5,5 @@ Setting `disableAnalytics` to true will prevent any data from being sent. */ - "disableAnalytics": false, - "liveReload": false /* Set to false for non web-socket browser debugging */ + "disableAnalytics": false } diff --git a/.npmignore b/.npmignore index 0533b91..a28e4ab 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,6 @@ bower_components/ tests/ +tmp/ .bowerrc .editorconfig diff --git a/.travis.yml b/.travis.yml index cf23938..70a305e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ --- language: node_js +node_js: + - "0.12" sudo: false diff --git a/Brocfile.js b/Brocfile.js index fdce144..042a64d 100644 --- a/Brocfile.js +++ b/Brocfile.js @@ -3,11 +3,7 @@ var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); -var app = new EmberAddon({ - vendorFiles: { - 'handlebars.js': null - } -}); +var app = new EmberAddon(); // Use `app.import` to add additional libraries to the generated // output files. diff --git a/addon/components/error-field.js b/addon/components/error-field.js new file mode 100644 index 0000000..dacfd9c --- /dev/null +++ b/addon/components/error-field.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; +import layout from '../templates/components/error-field'; + +export default Ember.Component.extend({ + classNameBindings: ['easyForm.errorClass'], + layout: layout, + tagName: 'span', + text: null, +}); diff --git a/addon/components/form-control.js b/addon/components/form-control.js new file mode 100644 index 0000000..8498e8d --- /dev/null +++ b/addon/components/form-control.js @@ -0,0 +1,66 @@ +import Ember from 'ember'; +import layout from '../templates/components/form-control'; + +var typeOf = Ember.typeOf; + +export default Ember.Component.extend({ + as: null, + layout: layout, + property: null, + showError: false, + + /* Input attributes */ + + collection: null, + optionValuePath: null, + optionLabelPath: null, + selection: null, + value: null, + multiple: null, + name: Ember.computed.oneWay('property'), + placeholder: null, + prompt: null, + disabled: null, + + type: Ember.computed('as', function() { + var as = this.get('as'); + var property = this.get('property'); + var type, value; + + if (!as) { + if (property.match(/password/)) { + type = 'password'; + } else if (property.match(/email/)) { + type = 'email'; + } else if (property.match(/url/)) { + type = 'url'; + } else if (property.match(/color/)) { + type = 'color'; + } else if (property.match(/^tel/) || property.match(/^phone/)) { + type = 'tel'; + } else if (property.match(/search/)) { + type = 'search'; + } else { + value = this.get('value'); + + if (typeOf(value) === 'number') { + type = 'number'; + } else if (typeOf(value) === 'date') { + type = 'date'; + } else if (typeOf(value) === 'boolean') { + type = 'checkbox'; + } + } + } else { + var inputType = this.get('easyForm.inputTypes.' + property); + + if (inputType) { + type = inputType; + } + + type = as; + } + + return type; + }), +}); diff --git a/addon/components/hint-field.js b/addon/components/hint-field.js new file mode 100644 index 0000000..05b00f5 --- /dev/null +++ b/addon/components/hint-field.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; +import layout from '../templates/components/hint-field'; + +export default Ember.Component.extend({ + classNameBindings: ['easyForm.hintClass'], + layout: layout, + tagName: 'span', + text: null, +}); diff --git a/addon/components/input-field.js b/addon/components/input-field.js new file mode 100644 index 0000000..632e57b --- /dev/null +++ b/addon/components/input-field.js @@ -0,0 +1,6 @@ +import Ember from 'ember'; +import layout from '../templates/components/input-field'; + +export default Ember.Component.extend({ + layout: layout +}); diff --git a/addon/components/label-field.js b/addon/components/label-field.js new file mode 100644 index 0000000..eb5a1f9 --- /dev/null +++ b/addon/components/label-field.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; +import layout from '../templates/components/label-field'; + +export default Ember.Component.extend({ + classNameBindings: ['easyForm.labelClass'], + layout: layout, + tagName: 'label', + text: Ember.computed.oneWay('parentView.property'), +}); diff --git a/addon/helpers/to-words.js b/addon/helpers/to-words.js new file mode 100644 index 0000000..f3cc7b4 --- /dev/null +++ b/addon/helpers/to-words.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; + +export function toWords(params) { + var string = params[0]; + + return string.underscore().split('_').join(' ').capitalize(); +} + +export default Ember.HTMLBars.makeBoundHelper(toWords); diff --git a/addon/initializers/easy-form-extensions.js b/addon/initializers/easy-form-extensions.js index e179338..090d317 100644 --- a/addon/initializers/easy-form-extensions.js +++ b/addon/initializers/easy-form-extensions.js @@ -1,6 +1,6 @@ import Ember from 'ember'; -export function initialize(/* container, app */) { +export function initialize(container, app) { var run = Ember.run; /** @@ -109,6 +109,8 @@ export function initialize(/* container, app */) { }); + app.inject('component', 'easyForm', 'service:easy-form'); + } export default { diff --git a/addon/services/easy-form.js b/addon/services/easy-form.js new file mode 100644 index 0000000..4f04bf6 --- /dev/null +++ b/addon/services/easy-form.js @@ -0,0 +1,11 @@ +import Ember from 'ember'; + +export default Ember.Service.extend({ + inputTypes: {}, + errorClass: 'error', + hintClass: 'hint', + inputClass: 'input', + inputWrapperClass: 'input-wrapper', + formClass: 'form', + formControlsClass: 'controls' +}); diff --git a/addon/templates/components/error-field.hbs b/addon/templates/components/error-field.hbs new file mode 100644 index 0000000..889d9ee --- /dev/null +++ b/addon/templates/components/error-field.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/addon/templates/components/form-control.hbs b/addon/templates/components/form-control.hbs new file mode 100644 index 0000000..889d9ee --- /dev/null +++ b/addon/templates/components/form-control.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/addon/templates/components/hint-field.hbs b/addon/templates/components/hint-field.hbs new file mode 100644 index 0000000..889d9ee --- /dev/null +++ b/addon/templates/components/hint-field.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/addon/templates/components/input-field.hbs b/addon/templates/components/input-field.hbs new file mode 100644 index 0000000..889d9ee --- /dev/null +++ b/addon/templates/components/input-field.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/addon/templates/components/label-field.hbs b/addon/templates/components/label-field.hbs new file mode 100644 index 0000000..4e1ac81 --- /dev/null +++ b/addon/templates/components/label-field.hbs @@ -0,0 +1 @@ +{{to-words text}} diff --git a/app/components/error-field.js b/app/components/error-field.js new file mode 100644 index 0000000..b6630d4 --- /dev/null +++ b/app/components/error-field.js @@ -0,0 +1,3 @@ +import errorField from 'ember-easy-form-extensions/components/error-field'; + +export default errorField; diff --git a/app/components/form-control.js b/app/components/form-control.js new file mode 100644 index 0000000..086e30a --- /dev/null +++ b/app/components/form-control.js @@ -0,0 +1,3 @@ +import formControl from 'ember-easy-form-extensions/components/form-control'; + +export default formControl; diff --git a/app/components/hint-field.js b/app/components/hint-field.js new file mode 100644 index 0000000..d02c4ea --- /dev/null +++ b/app/components/hint-field.js @@ -0,0 +1,3 @@ +import hintField from 'ember-easy-form-extensions/components/hint-field'; + +export default hintField; diff --git a/app/components/input-field.js b/app/components/input-field.js new file mode 100644 index 0000000..893ea63 --- /dev/null +++ b/app/components/input-field.js @@ -0,0 +1,3 @@ +import inputField from 'ember-easy-form-extensions/components/input-field'; + +export default inputField; diff --git a/app/components/label-field.js b/app/components/label-field.js new file mode 100644 index 0000000..aa87bb0 --- /dev/null +++ b/app/components/label-field.js @@ -0,0 +1,3 @@ +import labelField from 'ember-easy-form-extensions/components/label-field'; + +export default labelField; diff --git a/app/helpers/to-words.js b/app/helpers/to-words.js new file mode 100644 index 0000000..cf6d4c7 --- /dev/null +++ b/app/helpers/to-words.js @@ -0,0 +1,3 @@ +import toWords from 'ember-easy-form-extensions/helpers/to-words'; + +export default toWords; diff --git a/app/services/easy-form.js b/app/services/easy-form.js new file mode 100644 index 0000000..8e1123d --- /dev/null +++ b/app/services/easy-form.js @@ -0,0 +1,3 @@ +import easyForm from 'ember-easy-form-extensions/services/easy-form'; + +export default easyForm; diff --git a/bower.json b/bower.json index b26ae82..e1a7ec3 100644 --- a/bower.json +++ b/bower.json @@ -1,16 +1,16 @@ { "name": "ember-easy-form-extensions", "dependencies": { - "jquery": "^1.11.1", - "ember": "1.10.0", - "ember-data": "1.0.0-beta.15", - "ember-resolver": "~0.1.12", - "loader.js": "ember-cli/loader.js#3.2.0", + "ember": "1.11.1", "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", - "ember-load-initializers": "ember-cli/ember-load-initializers#0.0.2", - "ember-qunit": "0.2.8", + "ember-data": "1.0.0-beta.16.1", + "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.4", + "ember-qunit": "0.3.1", "ember-qunit-notifications": "0.0.7", + "ember-resolver": "~0.1.15", + "jquery": "^1.11.1", + "loader.js": "ember-cli/loader.js#3.2.0", "qunit": "~1.17.1" } } \ No newline at end of file diff --git a/index.js b/index.js index 9a97b38..d5dcd1b 100644 --- a/index.js +++ b/index.js @@ -3,13 +3,4 @@ module.exports = { name: 'ember-easy-form-extensions', - - included: function(app) { - - /* Because ember-easy-form isn't Ember CLI-ified */ - - app.import('vendor/ember-easy-form.js', { - type: 'vendor' - }); - } }; diff --git a/package.json b/package.json index 8e64d08..6b8a39b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "start": "ember server", "build": "ember build", - "test": "ember test" + "test": "ember try:testall" }, "repository": { "type": "git", @@ -21,22 +21,24 @@ "author": "Duncan Walker ", "license": "MIT", "dependencies": { + "ember-cli-babel": "^5.0.0", "ember-validations": "2.0.0-alpha.3" }, "devDependencies": { - "broccoli-asset-rev": "^2.0.0", - "ember-cli": "0.2.0", - "ember-cli-app-version": "0.3.2", - "ember-cli-babel": "^4.0.0", - "ember-cli-content-security-policy": "0.3.0", + "broccoli-asset-rev": "^2.0.2", + "ember-cli": "0.2.3", + "ember-cli-app-version": "0.3.3", + "ember-cli-content-security-policy": "0.4.0", "ember-cli-dependency-checker": "0.0.8", "ember-cli-htmlbars": "0.7.4", "ember-cli-ic-ajax": "0.1.1", "ember-cli-inject-live-reload": "^1.3.0", - "ember-cli-qunit": "0.3.9", + "ember-cli-qunit": "0.3.10", "ember-cli-uglify": "1.0.1", - "ember-data": "1.0.0-beta.15", - "ember-export-application-global": "^1.0.2" + "ember-data": "1.0.0-beta.16.1", + "ember-export-application-global": "^1.0.2", + "ember-disable-prototype-extensions": "^1.0.0", + "ember-try": "0.0.4" }, "keywords": [ "ember-addon", diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js index 757df38..8d66b95 100644 --- a/tests/dummy/app/app.js +++ b/tests/dummy/app/app.js @@ -3,9 +3,11 @@ import Resolver from 'ember/resolver'; import loadInitializers from 'ember/load-initializers'; import config from './config/environment'; +var App; + Ember.MODEL_FACTORY_INJECTIONS = true; -var App = Ember.Application.extend({ +App = Ember.Application.extend({ modulePrefix: config.modulePrefix, podModulePrefix: config.podModulePrefix, Resolver: Resolver diff --git a/tests/unit/components/error-field-test.js b/tests/unit/components/error-field-test.js new file mode 100644 index 0000000..6575a3c --- /dev/null +++ b/tests/unit/components/error-field-test.js @@ -0,0 +1,21 @@ +import { + moduleForComponent, + test +} from 'ember-qunit'; + +moduleForComponent('error-field', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'] +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); diff --git a/tests/unit/components/form-control-test.js b/tests/unit/components/form-control-test.js new file mode 100644 index 0000000..4ec65ba --- /dev/null +++ b/tests/unit/components/form-control-test.js @@ -0,0 +1,21 @@ +import { + moduleForComponent, + test +} from 'ember-qunit'; + +moduleForComponent('form-control', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'] +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); diff --git a/tests/unit/components/hint-field-test.js b/tests/unit/components/hint-field-test.js new file mode 100644 index 0000000..8aae867 --- /dev/null +++ b/tests/unit/components/hint-field-test.js @@ -0,0 +1,21 @@ +import { + moduleForComponent, + test +} from 'ember-qunit'; + +moduleForComponent('hint-field', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'] +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); diff --git a/tests/unit/components/input-field-test.js b/tests/unit/components/input-field-test.js new file mode 100644 index 0000000..7cc93ba --- /dev/null +++ b/tests/unit/components/input-field-test.js @@ -0,0 +1,21 @@ +import { + moduleForComponent, + test +} from 'ember-qunit'; + +moduleForComponent('input-field', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'] +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); diff --git a/tests/unit/components/label-field-test.js b/tests/unit/components/label-field-test.js new file mode 100644 index 0000000..60b79f0 --- /dev/null +++ b/tests/unit/components/label-field-test.js @@ -0,0 +1,21 @@ +import { + moduleForComponent, + test +} from 'ember-qunit'; + +moduleForComponent('label-field', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'] +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); diff --git a/tests/unit/helpers/to-words-test.js b/tests/unit/helpers/to-words-test.js new file mode 100644 index 0000000..2f78fe6 --- /dev/null +++ b/tests/unit/helpers/to-words-test.js @@ -0,0 +1,12 @@ +import { + toWords +} from '../../../helpers/to-words'; +import { module, test } from 'qunit'; + +module('ToWordsHelper'); + +// Replace this with your real tests. +test('it works', function(assert) { + var result = toWords(42); + assert.ok(result); +}); diff --git a/tests/unit/services/easy-form-test.js b/tests/unit/services/easy-form-test.js new file mode 100644 index 0000000..1a13047 --- /dev/null +++ b/tests/unit/services/easy-form-test.js @@ -0,0 +1,15 @@ +import { + moduleFor, + test +} from 'ember-qunit'; + +moduleFor('service:easy-form', { + // Specify the other units that are required for this test. + // needs: ['service:foo'] +}); + +// Replace this with your real tests. +test('it exists', function(assert) { + var service = this.subject(); + assert.ok(service); +}); diff --git a/vendor/ember-easy-form.js b/vendor/ember-easy-form.js deleted file mode 100644 index 15e3375..0000000 --- a/vendor/ember-easy-form.js +++ /dev/null @@ -1,644 +0,0 @@ -/* WARNING: This file has been edited - it is different the original Easy Form source. Some functionality has been removed to work better with ember-easy-form-extensions and changes have been made for HTMLBars compatibility. */ - -// ========================================================================== -// Project: Ember EasyForm -// Copyright: Copyright 2013 DockYard, LLC. and contributors. -// License: Licensed under MIT license (see license.js) -// ========================================================================== - -var EasyFormShims; - -(function() { - - EasyFormShims = { - - callHelper: function(helperName, context, params, options, env) { - env = env ? env : options; - - return Ember.Handlebars.helpers[helperName].helperFunction.call( - context, params, options.hash, options, options - ); - }, - - getBinding: function(options, propertyName) { - propertyName += 'Binding'; - - return this.getProperty(options, propertyName); - }, - - getProperty: function(options, name) { - var property = options.hash[name]; - - return options.data.view.getStream(property).value(); - }, - - viewHelper: function(context, View, options) { - return this.callHelper('view', context, [View], options); - }, - - emberInputHelper: function(context, options) { - var env = options; - - env.helpers = Ember.Handlebars.helpers; - - return this.callHelper('ember-input', context, [], options, env); - }, - } - -})(); - - -(function() { - -Ember.EasyForm = Ember.Namespace.create({ - VERSION: '1.0.0.beta.1' -}); - -})(); - - - -(function() { -Ember.EasyForm.Config = Ember.Namespace.create({ - _wrappers: { - 'default': { - formClass: '', - fieldErrorClass: 'fieldWithErrors', - inputClass: 'input', - errorClass: 'error', - hintClass: 'hint', - labelClass: '', - inputTemplate: 'easyForm/input', - errorTemplate: 'easyForm/error', - labelTemplate: 'easyForm/label', - hintTemplate: 'easyForm/hint', - wrapControls: false, - controlsWrapperClass: '', - buttonClass: '' - } - }, - modulePrefix: 'appkit', - _inputTypes: {}, - _templates: {}, - registerWrapper: function(name, wrapper) { - this._wrappers[name] = Ember.$.extend({}, this._wrappers['default'], wrapper); - }, - getWrapper: function(name) { - var wrapper = this._wrappers[name]; - Ember.assert("The wrapper '" + name + "' was not registered.", wrapper); - return wrapper; - }, - registerInputType: function(name, type){ - this._inputTypes[name] = type; - }, - getInputType: function(name) { - return this._inputTypes[name]; - }, - registerTemplate: function(name, template) { - this._templates[name] = template; - }, - getTemplate: function(name) { - return this._templates[name]; - } -}); - -})(); - - - -(function() { -Ember.Handlebars.registerHelper('error-field', function(property, options) { - options = Ember.EasyForm.processOptions(property, options); - - if (options.hash.propertyBinding) { - options.hash.property = EasyFormShims.getBinding(options, 'property'); - } - - return EasyFormShims.viewHelper(this, Ember.EasyForm.Error, options); -}); - -})(); - - - -(function() { -Ember.Handlebars.registerHelper('form-for', function(object, options) { - options.data.keywords.formForModelPath = object; - return EasyFormShims.viewHelper(this, Ember.EasyForm.Form, options); -}); - -})(); - - - -(function() { -Ember.Handlebars.registerHelper('hint-field', function(property, options) { - options = Ember.EasyForm.processOptions(property, options); - - if (options.hash.text || options.hash.textBinding) { - return EasyFormShims.viewHelper(this, Ember.EasyForm.Hint, options); - } -}); - -})(); - - - -(function() { -Ember.Handlebars.helpers['ember-input'] = Ember.Handlebars.helpers['input']; - -Ember.Handlebars.registerHelper('input', function(property, options) { - if (arguments.length === 1) { - options = property; - - return EasyFormShims.emberInputHelper(this, options); - } - - options = Ember.EasyForm.processOptions(property, options); - options.hash.isBlock = !!(options.fn); - - return EasyFormShims.viewHelper(this, Ember.EasyForm.Input, options); -}); - -})(); - - -(function() { -var get = Ember.get, - set = Ember.set; - -Ember.Handlebars.registerHelper('input-field', function(property, options) { - options = Ember.EasyForm.processOptions(property, options); - - if (options.hash.propertyBinding) { - options.hash.property = EasyFormShims.getBinding(options, 'property'); - } - - if (options.hash.inputOptionsBinding) { - options.hash.inputOptions = EasyFormShims.getBinding(options, 'inputOptions'); - } - - var modelPath = null; // CHANGED - - options.hash.modelPath = modelPath; - - property = options.hash.property; - - var modelPropertyPath = function(property) { - if(!property) { return null; } - - // CHANGED - var startsWithKeyword = options.data.keywords && !!options.data.keywords[property.split('.')[0]]; - - if (startsWithKeyword) { - return property; - } - - if (modelPath) { - return modelPath + '.' + property; - } else { - return property; - } - }; - - options.hash.valueBinding = modelPropertyPath(property); - - var context = this, - propertyType = function(property) { - var constructor = (get(context, 'content') || context).constructor; - - if (constructor.proto) { - return Ember.meta(constructor.proto(), false).descs[property]; - } else { - return null; - } - }; - - options.hash.viewName = 'input-field-'+options.data.view.elementId; - - if (options.hash.inputOptions) { - var inputOptions = options.hash.inputOptions, optionName; - for (optionName in inputOptions) { - if (inputOptions.hasOwnProperty(optionName)) { - options.hash[optionName] = inputOptions[optionName]; - } - } - delete options.hash.inputOptions; - } - - if (options.hash.as === 'text') { - return EasyFormShims.viewHelper(context, Ember.EasyForm.TextArea, options); - } else if (options.hash.as === 'select') { - delete(options.hash.valueBinding); - - options.hash.contentBinding = modelPropertyPath(options.hash.collection); - options.hash.selectionBinding = modelPropertyPath(options.hash.selection); - options.hash.valueBinding = modelPropertyPath(options.hash.value); - - if (Ember.isNone(options.hash.selectionBinding) && Ember.isNone(options.hash.valueBinding)) { - options.hash.selectionBinding = modelPropertyPath(property); - } - - return EasyFormShims.viewHelper(context, Ember.EasyForm.Select, options); - } else if (options.hash.as === 'checkbox') { - if (Ember.isNone(options.hash.checkedBinding)) { - options.hash.checkedBinding = modelPropertyPath(property); - } - - return EasyFormShims.viewHelper(context, Ember.EasyForm.Checkbox, options); - } else { - if (!options.hash.as) { - if (property.match(/password/)) { - options.hash.type = 'password'; - } else if (property.match(/email/)) { - options.hash.type = 'email'; - } else if (property.match(/url/)) { - options.hash.type = 'url'; - } else if (property.match(/color/)) { - options.hash.type = 'color'; - } else if (property.match(/^tel/)) { - options.hash.type = 'tel'; - } else if (property.match(/search/)) { - options.hash.type = 'search'; - } else { - if (propertyType(property) === 'number' || typeof(get(context,property)) === 'number') { - options.hash.type = 'number'; - } else if (propertyType(property) === 'date' || (!Ember.isNone(get(context,property)) && get(context,property).constructor === Date)) { - options.hash.type = 'date'; - } else if (propertyType(property) === 'boolean' || (!Ember.isNone(get(context,property)) && get(context,property).constructor === Boolean)) { - options.hash.checkedBinding = property; - return EasyFormShims.viewHelper(context, Ember.EasyForm.Checkbox, options); - } - } - } else { - var inputType = Ember.EasyForm.Config.getInputType(options.hash.as); - if (inputType) { - return EasyFormShims.viewHelper(context, inputType, options); - } - - options.hash.type = options.hash.as; - } - return EasyFormShims.viewHelper(context, Ember.EasyForm.TextField, options); - } -}); - -})(); - - - -(function() { -Ember.Handlebars.registerHelper('label-field', function(property, options) { - options = Ember.EasyForm.processOptions(property, options); - options.hash.viewName = 'label-field-'+options.data.view.elementId; - return EasyFormShims.viewHelper(this, Ember.EasyForm.Label, options); -}); - -})(); - - - -(function() { -Ember.Handlebars.registerHelper('submit', function(value, options) { - if (typeof(value) === 'object') { - options = value; - value = undefined; - } - options.hash.context = this; - options.hash.value = value || 'Submit'; - return (options.hash.as === 'button') ? - EasyFormShims.viewHelper(this, Ember.EasyForm.Button, options) - : - EasyFormShims.viewHelper(this, Ember.EasyForm.Submit, options); -}); - -})(); - - - -(function() { - -})(); - - - -(function() { -Ember.EasyForm.BaseView = Ember.View.extend({ - // classNameBindings: ['property'], - wrapper: function() { - var wrapperView = this.nearestWithProperty('wrapper'); - if (wrapperView) { - return wrapperView.get('wrapper'); - } else { - return 'default'; - } - }.property(), - wrapperConfig: function() { - return Ember.EasyForm.Config.getWrapper(this.get('wrapper')); - }.property('wrapper'), - templateForName: function(name) { - var template; - - if (this.container) { - template = this.container.lookup('template:' + name); - } - - return template || Ember.EasyForm.Config.getTemplate(name); - }, - formForModel: function(){ - var formForModelPath = this.get('templateData.keywords.formForModelPath'); - - if (formForModelPath === 'context' || formForModelPath === 'controller' || formForModelPath === 'this') { - return this.get('context'); - } else if (formForModelPath) { - return this.get('context.' + formForModelPath); - } else { - return this.get('context'); - } - }.property() -}); - -})(); - - - -(function() { -Ember.EasyForm.Checkbox = Ember.Checkbox.extend(); - -})(); - - - -(function() { -Ember.EasyForm.Error = Ember.EasyForm.BaseView.extend({ - tagName: 'span', - classNameBindings: ['wrapperConfig.errorClass'], - init: function() { - this._super(); - Ember.Binding.from('formForModel.errors.' + this.property).to('errors').connect(this); - }, - templateName: Ember.computed.oneWay('wrapperConfig.errorTemplate'), - errorText: Ember.computed.oneWay('errors.firstObject') -}); - -})(); - - - -(function() { -Ember.EasyForm.Form = Ember.EasyForm.BaseView.extend({ - tagName: 'form', - attributeBindings: ['novalidate'], - classNameBindings: ['wrapperConfig.formClass'], - novalidate: 'novalidate', - wrapper: 'default', - init: function() { - this._super(); - this.action = this.action || 'submit'; - }, - submit: function(event) { - var _this = this, - promise; - - if (event) { - event.preventDefault(); - } - - if (Ember.isNone(this.get('formForModel.validate'))) { - this.get('controller').send(this.action); - } else { - if (!Ember.isNone(this.get('formForModel').validate)) { - promise = this.get('formForModel').validate(); - } else { - promise = this.get('formForModel.content').validate(); - } - promise.then(function() { - if (_this.get('formForModel.isValid')) { - _this.get('controller').send(_this.action); - } - }); - } - } -}); - -})(); - - - -(function() { -Ember.EasyForm.Hint = Ember.EasyForm.BaseView.extend({ - tagName: 'span', - classNameBindings: ['wrapperConfig.hintClass'], - templateName: Ember.computed.oneWay('wrapperConfig.hintTemplate'), - hintText: Ember.computed.oneWay('text') -}); - -})(); - - - -(function() { -Ember.EasyForm.Input = Ember.EasyForm.BaseView.extend({ - init: function() { - this._super(); - this.classNameBindings.push('showError:' + this.get('wrapperConfig.fieldErrorClass')); - Ember.defineProperty(this, 'showError', Ember.computed.and('canShowValidationError', 'formForModel.errors.' + this.property + '.firstObject')); - if (!this.isBlock) { - this.set('templateName', this.get('wrapperConfig.inputTemplate')); - } - }, - setupValidationDependencies: function() { - var keys = this.get('formForModel._dependentValidationKeys'), key; - if (keys) { - for(key in keys) { - if (keys[key].contains(this.property)) { - this._keysForValidationDependencies.pushObject(key); - } - } - } - }.on('init'), - _keysForValidationDependencies: Ember.A(), - dependentValidationKeyCanTrigger: false, - tagName: 'div', - classNames: ['string'], - classNameBindings: ['wrapperConfig.inputClass'], - didInsertElement: function() { - var name = 'label-field-'+this.elementId, - label = this.get(name); - if (!label) { return; } - this.set(name+'.for', this.get('input-field-'+this.elementId+'.elementId')); - }, - concatenatedProperties: ['inputOptions', 'bindableInputOptions'], - inputOptions: ['as', 'collection', 'optionValuePath', 'optionLabelPath', 'selection', 'value', 'multiple', 'name'], - bindableInputOptions: ['placeholder', 'prompt', 'disabled'], - defaultOptions: { - name: function(){ - if (this.property) { - return this.property; - } - } - }, - inputOptionsValues: function() { - var options = {}, i, key, keyBinding, value, inputOptions = this.inputOptions, bindableInputOptions = this.bindableInputOptions, defaultOptions = this.defaultOptions; - for (i = 0; i < inputOptions.length; i++) { - key = inputOptions[i]; - if (this[key]) { - if (typeof(this[key]) === 'boolean') { - this[key] = key; - } - - options[key] = this[key]; - } - } - for (i = 0; i < bindableInputOptions.length; i++) { - key = bindableInputOptions[i]; - keyBinding = key + 'Binding'; - if (this[key] || this[keyBinding]) { - options[keyBinding] = 'view.' + key; - } - } - - for (key in defaultOptions) { - if (!defaultOptions.hasOwnProperty(key)) { continue; } - if (options[key]) { continue; } - - if (value = defaultOptions[key].apply(this)) { - options[key] = value; - } - } - - return options; - }.property(), - focusOut: function() { - this.set('hasFocusedOut', true); - this.showValidationError(); - }, - showValidationError: function() { - if (this.get('hasFocusedOut')) { - if (Ember.isEmpty(this.get('formForModel.errors.' + this.property))) { - this.set('canShowValidationError', false); - } else { - this.set('canShowValidationError', true); - } - } - }, - input: function() { - this._keysForValidationDependencies.forEach(function(key) { - this.get('parentView.childViews').forEach(function(view) { - if (view.property === key) { - view.showValidationError(); - } - }, this); - }, this); - } -}); - -})(); - - - -(function() { -Ember.EasyForm.Label = Ember.EasyForm.BaseView.extend({ - tagName: 'label', - attributeBindings: ['for'], - classNameBindings: ['wrapperConfig.labelClass'], - labelText: function() { - return this.get('text') || Ember.EasyForm.humanize(this.get('property')); - }.property('text', 'property'), - templateName: Ember.computed.oneWay('wrapperConfig.labelTemplate') -}); - -})(); - - - -(function() { -Ember.EasyForm.Select = Ember.Select.extend(); - -})(); - - - -(function() { -Ember.EasyForm.Submit = Ember.EasyForm.BaseView.extend({ - tagName: 'input', - attributeBindings: ['type', 'value', 'disabled'], - classNameBindings: ['wrapperConfig.buttonClass'], - type: 'submit', - disabled: function() { - return !this.get('formForModel.isValid'); - }.property('formForModel.isValid'), - init: function() { - this._super(); - this.set('value', this.value); - } -}); - -})(); - - -(function() { -Ember.EasyForm.TextArea = Ember.TextArea.extend(); - -})(); - - - -(function() { -Ember.EasyForm.TextField = Ember.TextField.extend(); - -})(); - - - -(function() { - -})(); - - -(function() { - -})(); - - - -(function() { -Ember.EasyForm.humanize = function(string) { - return string.underscore().split('_').join(' ').capitalize(); -}; - -Ember.EasyForm.eachTranslatedAttribute = function(object, fn) { - var isTranslatedAttribute = /(.+)Translation$/, - isTranslatedAttributeMatch; - - for (var key in object) { - isTranslatedAttributeMatch = key.match(isTranslatedAttribute); - if (isTranslatedAttributeMatch) { - fn.call(object, isTranslatedAttributeMatch[1], Ember.I18n.t(object[key])); - } - } -}; - -Ember.EasyForm.processOptions = function(property, options) { - if (options) { - if (Ember.I18n) { - var eachTranslatedAttribute = Ember.I18n.eachTranslatedAttribute || Ember.EasyForm.eachTranslatedAttribute; - eachTranslatedAttribute(options.hash, function (attribute, translation) { - options.hash[attribute] = translation; - delete options.hash[attribute + 'Translation']; - }); - } - options.hash.property = property; - } else { - options = property; - } - - return options; -}; - -})(); - - - -(function() { - -})(); From 09e2797a8a3d2fad1d913644dcca6760ced23bc4 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sun, 10 May 2015 20:51:28 -0400 Subject: [PATCH 02/73] Prototype extensions removed --- addon/components/error-field.js | 14 ++- addon/components/form-controls.js | 8 +- addon/components/form-submission.js | 14 ++- .../{form-control.js => input-wrapper.js} | 47 ++++++++ addon/initializers/easy-form-extensions.js | 109 +----------------- addon/mixins/controllers/saving.js | 37 +++--- addon/mixins/routes/delete-record.js | 4 +- addon/mixins/routes/rollback.js | 4 +- addon/mixins/views/submitting.js | 8 +- addon/services/easy-form.js | 2 +- addon/utils/observers/soft-assert.js | 64 ++++++++++ addon/utils/to-words.js | 3 + package.json | 4 +- 13 files changed, 170 insertions(+), 148 deletions(-) rename addon/components/{form-control.js => input-wrapper.js} (54%) create mode 100644 addon/utils/observers/soft-assert.js create mode 100644 addon/utils/to-words.js diff --git a/addon/components/error-field.js b/addon/components/error-field.js index dacfd9c..9952e6b 100644 --- a/addon/components/error-field.js +++ b/addon/components/error-field.js @@ -1,9 +1,21 @@ +import defaultFor from '../utils/default-for'; import Ember from 'ember'; import layout from '../templates/components/error-field'; +import toWords from '../utils/to-words'; export default Ember.Component.extend({ classNameBindings: ['easyForm.errorClass'], + error: null, layout: layout, + property: null, tagName: 'span', - text: null, + + errorText: Ember.computed('errors.[]', 'value', function() { + var propertyName = defaultFor( + this.get('property'), + this.get('parentView.property') + ); + + return toWords(propertyName) + ' ' + this.get('error'); + }), }); diff --git a/addon/components/form-controls.js b/addon/components/form-controls.js index 2f32d9a..c47af9b 100644 --- a/addon/components/form-controls.js +++ b/addon/components/form-controls.js @@ -1,4 +1,5 @@ import Ember from 'ember'; +import softAssert from '../utils/observers/soft-assert'; export default Ember.Component.extend({ attributeBindings: ['legend'], @@ -7,10 +8,5 @@ export default Ember.Component.extend({ legend: null, tagName: 'fieldset', - checkForLegend: function() { - Ember.assert( - 'You must pass a legend (description) to the form-controls component like {{#form-controls legend=\'Write a new blog post\'}}', - this.get('legend') - ); - }.on('didInsertElement') + checkForLegend: softAssert('legend') }); diff --git a/addon/components/form-submission.js b/addon/components/form-submission.js index 8af5a5f..a1a8c92 100644 --- a/addon/components/form-submission.js +++ b/addon/components/form-submission.js @@ -17,11 +17,13 @@ export default Ember.Component.extend( } }, - _watchForEmptyComponent: function() { - Ember.warn( - 'The {{form-submission}} component is not showing the submit or the cancel button.', - this.get('cancel') || this.get('submit') - ); - }.observes('cancel', 'submit'), + _watchForEmptyComponent: Ember.observer('cancel', 'submit', + function() { + Ember.warn( + 'The {{form-submission}} component is not showing the submit or the cancel button.', + this.get('cancel') || this.get('submit') + ); + } + ), }); diff --git a/addon/components/form-control.js b/addon/components/input-wrapper.js similarity index 54% rename from addon/components/form-control.js rename to addon/components/input-wrapper.js index 8498e8d..290bac0 100644 --- a/addon/components/form-control.js +++ b/addon/components/input-wrapper.js @@ -2,12 +2,19 @@ import Ember from 'ember'; import layout from '../templates/components/form-control'; var typeOf = Ember.typeOf; +var run = Ember.run; export default Ember.Component.extend({ as: null, layout: layout, property: null, showError: false, + showValidity: false, + + classNameBindings: [ + 'easyForm.inputWrapperClass', + 'showValidity:control-valid' + ], /* Input attributes */ @@ -63,4 +70,44 @@ export default Ember.Component.extend({ return type; }), + + setInvalidToValid: Ember.observer('showError', function() { + // If we go from error to no error + if (!this.get('showError') && this.get('canShowValidationError')) { + run.debounce(this, function() { + var hasAnError = this.get('formForModel.errors.' + this.get('property') + '.length'); + + if (!hasAnError && !this.get('isDestroying')) { + this.set('showValidity', true); + + run.later(this, function() { + if (!this.get('isDestroying')) { + this.set('showValidity', false); + } + }, 2000); + } + }, 50); + } + }), + + /** + An override of easyForm's default `focusOut` method to ensure validations are not shown when the user clicks cancel. + + @method focusOut + */ + + focusOut: function() { + + /* Hacky - delay check so focusOut runs after the cancel action */ + + run.later(this, function() { + var cancelClicked = this.get('parentView.cancelClicked'); + var isDestroying = this.get('isDestroying'); + + if (!cancelClicked && !isDestroying) { + this.set('hasFocusedOut', true); + this.showValidationError(); + } + }, 100); + }, }); diff --git a/addon/initializers/easy-form-extensions.js b/addon/initializers/easy-form-extensions.js index 090d317..bf8ca59 100644 --- a/addon/initializers/easy-form-extensions.js +++ b/addon/initializers/easy-form-extensions.js @@ -3,117 +3,10 @@ import Ember from 'ember'; export function initialize(container, app) { var run = Ember.run; - /** - Default option overrides - */ - - Ember.EasyForm.Config.registerWrapper('default', { - errorClass: 'error', - errorTemplate: 'easy-form/error', - - formClass: 'form', - fieldErrorClass: 'control-error', - - hintClass: 'hint', - hintTemplate: 'easy-form/hint', - - inputClass: 'control', - inputTemplate: 'easy-form/input', - - labelClass: 'label', - labelTemplate: 'easy-form/label' - }); - - Ember.EasyForm.Checkbox.reopen({ - attributeBindings: ['dataTest:data-test'], - classNames: ['input-checkbox'], - dataTest: Ember.computed.alias('parentView.dataTest'), - }); - - Ember.EasyForm.TextField.reopen({ - attributeBindings: ['dataTest:data-test'], - classNames: ['input'], - dataTest: Ember.computed.alias('parentView.dataTest'), - }); - - Ember.EasyForm.TextArea.reopen({ - attributeBindings: ['dataTest:data-test'], - classNames: ['input-textarea'], - dataTest: Ember.computed.alias('parentView.dataTest'), - }); - - /** - Overrides the original `errorText` property to add the property name to the error message. For example: - - can't be blank --> Name can't be blank - must be a number --> Age must be a number - - If a label is specified on the input, this will be used in place of the property name. - */ - - Ember.EasyForm.Error.reopen({ - errorText: function() { - var propertyName = this.get('parentView.label') || this.get('property') || ''; - - return Ember.EasyForm.humanize(propertyName) + ' ' + this.get('errors.firstObject'); - }.property('errors.[]', 'value'), - }); - - /** - Temporarily binds a success class the the control when the input goes from invalid to valid. - */ - - Ember.EasyForm.Input.reopen({ - classNameBindings: ['showValidity:control-valid'], - showValidity: false, - - setInvalidToValid: function() { - // If we go from error to no error - if (!this.get('showError') && this.get('canShowValidationError')) { - run.debounce(this, function() { - var hasAnError = this.get('formForModel.errors.' + this.get('property') + '.length'); - - if (!hasAnError && !this.get('isDestroying')) { - this.set('showValidity', true); - - run.later(this, function() { - if (!this.get('isDestroying')) { - this.set('showValidity', false); - } - }, 2000); - } - }, 50); - } - }.observes('showError'), - - /** - An override of easyForm's default `focusOut` method to ensure validations are not shown when the user clicks cancel. - - @method focusOut - */ - - focusOut: function() { - - /* Hacky - delay check so focusOut runs after the cancel action */ - - run.later(this, function() { - var cancelClicked = this.get('parentView.cancelClicked'); - var isDestroying = this.get('isDestroying'); - - if (!cancelClicked && !isDestroying) { - this.set('hasFocusedOut', true); - this.showValidationError(); - } - }, 100); - }, - - }); - app.inject('component', 'easyForm', 'service:easy-form'); - } export default { - name: 'easy-form-extensions', + name: 'easy-form', initialize: initialize }; diff --git a/addon/mixins/controllers/saving.js b/addon/mixins/controllers/saving.js index 56ae886..347ec6f 100644 --- a/addon/mixins/controllers/saving.js +++ b/addon/mixins/controllers/saving.js @@ -17,13 +17,13 @@ export default Ember.Mixin.create( } }, - editingModel: function() { + editingModel: Ember.computed(function() { return this.toString().indexOf('/edit:') > -1; - }.property().readOnly(), + }), - newModel: function() { + newModel: Ember.computed(function() { return this.toString().indexOf('/new:') > -1; - }.property().readOnly(), + }), showServerError: function(/* xhr */) { this.set('formSubmitted', false); @@ -71,20 +71,25 @@ export default Ember.Mixin.create( } }, - _revalidate: function() { - var _this = this; + // TODO - add observes revalidateFor.[] + _revalidate: Ember.on('routeDidTransition', + function() { + var _this = this; - _this.forEachRevalidator(function(property) { - _this.addObserver(property, _this.validate); - }); - }.observes('revalidateFor.[]').on('routeDidTransition'), + _this.forEachRevalidator(function(property) { + _this.addObserver(property, _this.validate); + }); + } + ), - _removeRevalidationObservers: function() { - var _this = this; + _removeRevalidationObservers: Ember.on('routeWillTransition', + function() { + var _this = this; - _this.forEachRevalidator(function(property) { - _this.removeObserver(property, _this.validate); - }); - }.on('routeWillTransition'), + _this.forEachRevalidator(function(property) { + _this.removeObserver(property, _this.validate); + }); + } + ), }); diff --git a/addon/mixins/routes/delete-record.js b/addon/mixins/routes/delete-record.js index 88c4e6f..dd850bf 100644 --- a/addon/mixins/routes/delete-record.js +++ b/addon/mixins/routes/delete-record.js @@ -2,12 +2,12 @@ import Ember from 'ember'; export default Ember.Mixin.create({ - deleteRecord: function() { + deleteRecord: Ember.on('willTransition', function() { var model = this.get('controller.content'); if (model.get('isDirty')) { model.deleteRecord(); } - }.on('willTransition'), + }), }); diff --git a/addon/mixins/routes/rollback.js b/addon/mixins/routes/rollback.js index 4df51de..7d1f050 100644 --- a/addon/mixins/routes/rollback.js +++ b/addon/mixins/routes/rollback.js @@ -2,12 +2,12 @@ import Ember from 'ember'; export default Ember.Mixin.create({ - rollback: function() { + rollback: Ember.on('willTransition', function() { var model = this.get('controller.content'); if (model.get('isDirty')) { model.rollback(); } - }.on('willTransition'), + }), }); diff --git a/addon/mixins/views/submitting.js b/addon/mixins/views/submitting.js index f953a2a..6c90c77 100644 --- a/addon/mixins/views/submitting.js +++ b/addon/mixins/views/submitting.js @@ -25,13 +25,13 @@ export default Ember.Mixin.create({ /* Autofocus on the first input */ - autofocus: function() { + autofocus: Ember.on('didInsertElement', function() { var input = this.$().find('input').first(); if (!Ember.$(input).hasClass('datepicker')) { input.focus(); } - }.on('didInsertElement'), + }), /* Show validation errors on submit click */ @@ -55,9 +55,9 @@ export default Ember.Mixin.create({ this._eventHandler('submit'); }, - resetForm: function() { + resetForm: Ember.on('willInsertElement', function() { this.set('formSubmitted', false); - }.on('willInsertElement'), + }), /* Private methods */ diff --git a/addon/services/easy-form.js b/addon/services/easy-form.js index 4f04bf6..b9895bc 100644 --- a/addon/services/easy-form.js +++ b/addon/services/easy-form.js @@ -5,7 +5,7 @@ export default Ember.Service.extend({ errorClass: 'error', hintClass: 'hint', inputClass: 'input', - inputWrapperClass: 'input-wrapper', + controlClass: 'input-wrapper', formClass: 'form', formControlsClass: 'controls' }); diff --git a/addon/utils/observers/soft-assert.js b/addon/utils/observers/soft-assert.js new file mode 100644 index 0000000..226f856 --- /dev/null +++ b/addon/utils/observers/soft-assert.js @@ -0,0 +1,64 @@ +/** +Checks whether a property is present on a class and shows a warning to the developer when it is not. In production environments this does nothing. + +Options can be pased as a second parameter: + +```js +Ember.Component.extend({ + checkForDescription: softAssert('descriptions', { + eventName: 'didInsertElement', // Defaults to 'init' + onTrue: function() { + this.set('hasCorrectProperties', true); + }, + onFalse: function() { + this.set('hasCorrectProperties', false); + } + }); +}); +``` + +@method Utils.computed.softAssert +@param {String} dependentKey The name of the Ember property to observe +@param {Object} options An object containing options for your assertion +*/ + +import defaultFor from '../default-for'; +import Ember from 'ember'; +import ENV from 'ember-easy-form-extensions/config/environment'; + +var macro; + +if (ENV.environment === 'development') { + + macro = Ember.on(eventName, Ember.observer(dependentKey, + function(dependentKey, options) { + var eventName; + + options = defaultFor(options, {}); + eventName = defaultFor(options.eventName, 'init'); + + return function() { + var value = defaultFor(this.get(dependentKey), ''); + var constructor; + + if (!value) { + constructor = this.get('constructor').toString(); + + Ember.warn( + 'You failed to pass a ' + dependentKey +' property to ' + constructor + ); + + if (options.onTrue) { + callbacks.onTrue().bind(this); + } + } else if (options.onFalse) { + callbacks.onFalse().bind(this); + } + } + } + )); +} else { + macro = Ember.K +} + +export default macro; diff --git a/addon/utils/to-words.js b/addon/utils/to-words.js new file mode 100644 index 0000000..79edaec --- /dev/null +++ b/addon/utils/to-words.js @@ -0,0 +1,3 @@ +export default function() { + return string.underscore().split('_').join(' ').capitalize(); +} diff --git a/package.json b/package.json index 6b8a39b..6621836 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "license": "MIT", "dependencies": { "ember-cli-babel": "^5.0.0", + "ember-cli-htmlbars": "0.7.6", "ember-validations": "2.0.0-alpha.3" }, "devDependencies": { @@ -30,14 +31,13 @@ "ember-cli-app-version": "0.3.3", "ember-cli-content-security-policy": "0.4.0", "ember-cli-dependency-checker": "0.0.8", - "ember-cli-htmlbars": "0.7.4", "ember-cli-ic-ajax": "0.1.1", "ember-cli-inject-live-reload": "^1.3.0", "ember-cli-qunit": "0.3.10", "ember-cli-uglify": "1.0.1", "ember-data": "1.0.0-beta.16.1", - "ember-export-application-global": "^1.0.2", "ember-disable-prototype-extensions": "^1.0.0", + "ember-export-application-global": "^1.0.2", "ember-try": "0.0.4" }, "keywords": [ From cdda59001d89c6057c5ed1183661a51cd247b80d Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sun, 10 May 2015 21:42:57 -0400 Subject: [PATCH 03/73] Component rendering --- addon/components/error-field.js | 2 +- addon/components/input-wrapper.js | 2 +- addon/mixins/controllers/saving.js | 2 +- addon/mixins/views/walk-views.js | 4 +- addon/templates/components/form-control.hbs | 1 - addon/templates/components/input-wrapper.hbs | 10 ++++ addon/utils/observers/soft-assert.js | 49 +++++++++---------- app/components/form-control.js | 3 -- app/components/input-wrapper.js | 3 ++ .../app/resources/posts/new/template.hbs | 6 +-- 10 files changed, 45 insertions(+), 37 deletions(-) delete mode 100644 addon/templates/components/form-control.hbs create mode 100644 addon/templates/components/input-wrapper.hbs delete mode 100644 app/components/form-control.js create mode 100644 app/components/input-wrapper.js diff --git a/addon/components/error-field.js b/addon/components/error-field.js index 9952e6b..1cc61e6 100644 --- a/addon/components/error-field.js +++ b/addon/components/error-field.js @@ -10,7 +10,7 @@ export default Ember.Component.extend({ property: null, tagName: 'span', - errorText: Ember.computed('errors.[]', 'value', function() { + text: Ember.computed('errors.[]', 'value', function() { var propertyName = defaultFor( this.get('property'), this.get('parentView.property') diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index 290bac0..4fbb457 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -1,5 +1,5 @@ import Ember from 'ember'; -import layout from '../templates/components/form-control'; +import layout from '../templates/components/input-wrapper'; var typeOf = Ember.typeOf; var run = Ember.run; diff --git a/addon/mixins/controllers/saving.js b/addon/mixins/controllers/saving.js index 347ec6f..00c9664 100644 --- a/addon/mixins/controllers/saving.js +++ b/addon/mixins/controllers/saving.js @@ -10,7 +10,7 @@ export default Ember.Mixin.create( forEachRevalidator: function(callback) { var revalidateFor = this.get('revalidateFor'); - if (revalidateFor.get('length')) { + if (revalidateFor.length) { revalidateFor.forEach(function(property) { callback(property); }); diff --git a/addon/mixins/views/walk-views.js b/addon/mixins/views/walk-views.js index 4d4d0e7..6d18911 100644 --- a/addon/mixins/views/walk-views.js +++ b/addon/mixins/views/walk-views.js @@ -2,9 +2,9 @@ import Ember from 'ember'; export default Ember.Mixin.create({ - formView: function() { + formView: Ember.computed(function() { return this.walkViews(this.get('parentView')); - }.property(), + }), walkViews: function(view) { var parentView; diff --git a/addon/templates/components/form-control.hbs b/addon/templates/components/form-control.hbs deleted file mode 100644 index 889d9ee..0000000 --- a/addon/templates/components/form-control.hbs +++ /dev/null @@ -1 +0,0 @@ -{{yield}} diff --git a/addon/templates/components/input-wrapper.hbs b/addon/templates/components/input-wrapper.hbs new file mode 100644 index 0000000..b24fd99 --- /dev/null +++ b/addon/templates/components/input-wrapper.hbs @@ -0,0 +1,10 @@ +{{#if label}} + {{label-field}} +{{/if}} + +{{yield}} +{{log this}} + +{{#if error}} + {{error-field}} +{{/if}} diff --git a/addon/utils/observers/soft-assert.js b/addon/utils/observers/soft-assert.js index 226f856..35682a7 100644 --- a/addon/utils/observers/soft-assert.js +++ b/addon/utils/observers/soft-assert.js @@ -24,41 +24,40 @@ Ember.Component.extend({ import defaultFor from '../default-for'; import Ember from 'ember'; -import ENV from 'ember-easy-form-extensions/config/environment'; +// import ENV from '../../config/environment'; var macro; -if (ENV.environment === 'development') { +// TODO +// if (ENV.environment === 'development') { - macro = Ember.on(eventName, Ember.observer(dependentKey, - function(dependentKey, options) { - var eventName; + macro = function(dependentKey, options) { + var eventName; - options = defaultFor(options, {}); - eventName = defaultFor(options.eventName, 'init'); + options = defaultFor(options, {}); + eventName = defaultFor(options.eventName, 'init'); - return function() { - var value = defaultFor(this.get(dependentKey), ''); - var constructor; + return Ember.on(eventName, Ember.observer(dependentKey, function() { + var value = defaultFor(this.get(dependentKey), ''); + var constructor; - if (!value) { - constructor = this.get('constructor').toString(); + if (!value) { + constructor = this.get('constructor').toString(); - Ember.warn( - 'You failed to pass a ' + dependentKey +' property to ' + constructor - ); + Ember.warn( + 'You failed to pass a ' + dependentKey +' property to ' + constructor + ); - if (options.onTrue) { - callbacks.onTrue().bind(this); - } - } else if (options.onFalse) { - callbacks.onFalse().bind(this); + if (options.onTrue) { + callbacks.onTrue().bind(this); } + } else if (options.onFalse) { + callbacks.onFalse().bind(this); } - } - )); -} else { - macro = Ember.K -} + })); + }; +// } else { +// macro = Ember.K +// } export default macro; diff --git a/app/components/form-control.js b/app/components/form-control.js deleted file mode 100644 index 086e30a..0000000 --- a/app/components/form-control.js +++ /dev/null @@ -1,3 +0,0 @@ -import formControl from 'ember-easy-form-extensions/components/form-control'; - -export default formControl; diff --git a/app/components/input-wrapper.js b/app/components/input-wrapper.js new file mode 100644 index 0000000..a842796 --- /dev/null +++ b/app/components/input-wrapper.js @@ -0,0 +1,3 @@ +import inputWrapper from 'ember-easy-form-extensions/components/input-wrapper'; + +export default inputWrapper; diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index 2097ecc..a71da8c 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -1,9 +1,9 @@ {{#form-wrapper}} {{#form-controls legend='Write a new post'}} - {{input title}} - {{input description}} - {{input valueBinding=title}} + {{input-wrapper property=title}} + {{input-wrapper property=description}} + {{input-wrapper property=title}} {{/form-controls}} {{form-submission}} From f1f87cc70e4c6a27451eba157258019855fe62a7 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sun, 10 May 2015 23:02:52 -0400 Subject: [PATCH 04/73] Label component added --- addon/components/form-controls.js | 23 +++++++++++++++---- addon/components/form-wrapper.js | 2 +- addon/components/input-wrapper.js | 11 ++++++++- addon/components/label-field.js | 2 +- addon/services/easy-form.js | 4 ++-- addon/templates/components/input-wrapper.hbs | 13 +++++++---- addon/templates/components/label-field.hbs | 2 +- addon/utils/to-words.js | 6 +++-- .../app/resources/posts/new/template.hbs | 8 ++++--- 9 files changed, 52 insertions(+), 19 deletions(-) diff --git a/addon/components/form-controls.js b/addon/components/form-controls.js index c47af9b..4b1b602 100644 --- a/addon/components/form-controls.js +++ b/addon/components/form-controls.js @@ -1,12 +1,27 @@ import Ember from 'ember'; import softAssert from '../utils/observers/soft-assert'; +import WalkViews from '../mixins/views/walk-views'; + +export default Ember.Component.extend( + WalkViews, { -export default Ember.Component.extend({ attributeBindings: ['legend'], - classNameBindings: ['className'], - className: 'controls', + classNameBindings: ['easyForm.formControlsClass'], legend: null, + model: null, tagName: 'fieldset', + checkForLegend: softAssert('legend'), + + findDefaultModel: Ember.on('willInsertElement', function() { + var isFulfilled, modelIsAPromise; + + if (!this.get('model')) { + isFulfilled = this.get('model.isFulfilled'); + modelIsAPromise = Ember.typeOf(isFulfilled === 'boolean'); - checkForLegend: softAssert('legend') + if (!modelIsAPromise) { + this.set('model', this.get('formView.controller.model')); + } + } + }), }); diff --git a/addon/components/form-wrapper.js b/addon/components/form-wrapper.js index b6758a7..824a90d 100644 --- a/addon/components/form-wrapper.js +++ b/addon/components/form-wrapper.js @@ -2,7 +2,7 @@ import Ember from 'ember'; export default Ember.Component.extend({ attributeBindings: ['novalidate'], - classNameBindings: ['className'], + classNameBindings: ['easyForm.formWrapperClass'], className: 'form', novalidate: true, tagName: 'form', diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index 4fbb457..1ed3af1 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -1,5 +1,7 @@ +import defaultFor from '../utils/default-for'; import Ember from 'ember'; import layout from '../templates/components/input-wrapper'; +import toWords from '../utils/to-words'; var typeOf = Ember.typeOf; var run = Ember.run; @@ -7,9 +9,9 @@ var run = Ember.run; export default Ember.Component.extend({ as: null, layout: layout, - property: null, showError: false, showValidity: false, + value: null, classNameBindings: [ 'easyForm.inputWrapperClass', @@ -26,9 +28,14 @@ export default Ember.Component.extend({ multiple: null, name: Ember.computed.oneWay('property'), placeholder: null, + property: Ember.computed.oneWay('valueBinding._label'), prompt: null, disabled: null, + label: Ember.computed('property', function() { + return toWords(defaultFor(this.get('property'), '')); + }), + type: Ember.computed('as', function() { var as = this.get('as'); var property = this.get('property'); @@ -110,4 +117,6 @@ export default Ember.Component.extend({ } }, 100); }, + + showValidationError: Ember.K }); diff --git a/addon/components/label-field.js b/addon/components/label-field.js index eb5a1f9..247c5fc 100644 --- a/addon/components/label-field.js +++ b/addon/components/label-field.js @@ -3,7 +3,7 @@ import layout from '../templates/components/label-field'; export default Ember.Component.extend({ classNameBindings: ['easyForm.labelClass'], + label: null, layout: layout, tagName: 'label', - text: Ember.computed.oneWay('parentView.property'), }); diff --git a/addon/services/easy-form.js b/addon/services/easy-form.js index b9895bc..9905f8b 100644 --- a/addon/services/easy-form.js +++ b/addon/services/easy-form.js @@ -5,7 +5,7 @@ export default Ember.Service.extend({ errorClass: 'error', hintClass: 'hint', inputClass: 'input', - controlClass: 'input-wrapper', - formClass: 'form', + inputWrapperClass: 'input-wrapper', + formWrapperClass: 'form', formControlsClass: 'controls' }); diff --git a/addon/templates/components/input-wrapper.hbs b/addon/templates/components/input-wrapper.hbs index b24fd99..60297ba 100644 --- a/addon/templates/components/input-wrapper.hbs +++ b/addon/templates/components/input-wrapper.hbs @@ -1,10 +1,15 @@ {{#if label}} - {{label-field}} + {{label-field label=label}} {{/if}} -{{yield}} -{{log this}} +{{#if template}} + {{!-- If block form... --}} + {{yield}} +{{else}} + {{!-- ...else show input --}} + {{input}} +{{/if}} {{#if error}} - {{error-field}} + {{error-field property=property}} {{/if}} diff --git a/addon/templates/components/label-field.hbs b/addon/templates/components/label-field.hbs index 4e1ac81..21f81c8 100644 --- a/addon/templates/components/label-field.hbs +++ b/addon/templates/components/label-field.hbs @@ -1 +1 @@ -{{to-words text}} +{{label}} diff --git a/addon/utils/to-words.js b/addon/utils/to-words.js index 79edaec..927850e 100644 --- a/addon/utils/to-words.js +++ b/addon/utils/to-words.js @@ -1,3 +1,5 @@ -export default function() { - return string.underscore().split('_').join(' ').capitalize(); +var String = Ember.String; + +export default function(string) { + return String.capitalize(String.underscore(string).split('_').join(' ')); } diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index a71da8c..ea24446 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -1,9 +1,11 @@ {{#form-wrapper}} {{#form-controls legend='Write a new post'}} - {{input-wrapper property=title}} - {{input-wrapper property=description}} - {{input-wrapper property=title}} + {{input-wrapper value=title}} + {{!-- {{input-wrapper property=description}} + {{#input-wrapper property=title}} + hey + {{/input-wrapper}} --}} {{/form-controls}} {{form-submission}} From 1d1a4effe1e97070bf7d8f3e47ee35f4d32b9125 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Tue, 12 May 2015 00:06:27 -0400 Subject: [PATCH 05/73] Error observing and component templates moved to addon tree --- addon/components/error-field.js | 24 +++++++++++++++++- addon/components/form-controls.js | 25 ++++++++++--------- addon/components/input-wrapper.js | 18 +++++++++++-- addon/helpers/capitalize-string.js | 9 +++++++ addon/helpers/to-words.js | 9 ------- .../components/destroy-submission.hbs | 0 addon/templates/components/error-field.hbs | 4 ++- .../templates/components/form-controls.hbs | 0 .../templates/components/form-submission.hbs | 0 .../templates/components/form-wrapper.hbs | 0 addon/templates/components/input-wrapper.hbs | 13 +++++++--- addon/templates/components/label-field.hbs | 2 +- .../templates/components/loading-spinner.hbs | 0 addon/utils/to-words.js | 7 +++--- app/helpers/capitalize-string.js | 3 +++ app/helpers/to-words.js | 3 --- app/templates/easy-form/error.hbs | 1 - app/templates/easy-form/hint.hbs | 1 - app/templates/easy-form/input-controls.hbs | 15 ----------- app/templates/easy-form/input.hbs | 5 ---- app/templates/easy-form/label.hbs | 1 - .../app/resources/post/edit/controller.js | 5 ++-- .../app/resources/posts/index/controller.js | 2 +- .../app/resources/posts/index/template.hbs | 2 +- .../app/resources/posts/new/controller.js | 5 ++-- .../app/resources/posts/new/template.hbs | 10 +++++--- 26 files changed, 94 insertions(+), 70 deletions(-) create mode 100644 addon/helpers/capitalize-string.js delete mode 100644 addon/helpers/to-words.js rename {app => addon}/templates/components/destroy-submission.hbs (100%) rename {app => addon}/templates/components/form-controls.hbs (100%) rename {app => addon}/templates/components/form-submission.hbs (100%) rename {app => addon}/templates/components/form-wrapper.hbs (100%) rename {app => addon}/templates/components/loading-spinner.hbs (100%) create mode 100644 app/helpers/capitalize-string.js delete mode 100644 app/helpers/to-words.js delete mode 100644 app/templates/easy-form/error.hbs delete mode 100644 app/templates/easy-form/hint.hbs delete mode 100644 app/templates/easy-form/input-controls.hbs delete mode 100644 app/templates/easy-form/input.hbs delete mode 100644 app/templates/easy-form/label.hbs diff --git a/addon/components/error-field.js b/addon/components/error-field.js index 1cc61e6..3e09a30 100644 --- a/addon/components/error-field.js +++ b/addon/components/error-field.js @@ -2,10 +2,14 @@ import defaultFor from '../utils/default-for'; import Ember from 'ember'; import layout from '../templates/components/error-field'; import toWords from '../utils/to-words'; +import WalkViews from '../mixins/views/walk-views'; + +export default Ember.Component.extend( + WalkViews, { -export default Ember.Component.extend({ classNameBindings: ['easyForm.errorClass'], error: null, + label: Ember.computed.oneWay('property'), layout: layout, property: null, tagName: 'span', @@ -18,4 +22,22 @@ export default Ember.Component.extend({ return toWords(propertyName) + ' ' + this.get('error'); }), + + addErrorObserver: Ember.on('init', function() { + var fullPropertyPath = this.get('parentView.fullPropertyPath'); + var controller, errorPath, setError; + + if (fullPropertyPath) { + controller = this.get('formView.controller'); + + errorPath = 'errors.' + fullPropertyPath + '.firstObject'; + + setError = function() { + this.set('error', controller.get(errorPath)); + }; + + // TODO - Remove observer? + controller.addObserver(errorPath, this, setError); + } + }) }); diff --git a/addon/components/form-controls.js b/addon/components/form-controls.js index 4b1b602..f80b543 100644 --- a/addon/components/form-controls.js +++ b/addon/components/form-controls.js @@ -9,19 +9,20 @@ export default Ember.Component.extend( classNameBindings: ['easyForm.formControlsClass'], legend: null, model: null, + modelPath: Ember.computed.oneWay('modelBinding._label'), tagName: 'fieldset', checkForLegend: softAssert('legend'), - findDefaultModel: Ember.on('willInsertElement', function() { - var isFulfilled, modelIsAPromise; - - if (!this.get('model')) { - isFulfilled = this.get('model.isFulfilled'); - modelIsAPromise = Ember.typeOf(isFulfilled === 'boolean'); - - if (!modelIsAPromise) { - this.set('model', this.get('formView.controller.model')); - } - } - }), + // findDefaultModel: Ember.on('init', function() { + // var isFulfilled, modelIsAPromise; + // + // if (!this.get('model')) { + // isFulfilled = this.get('model.isFulfilled'); + // modelIsAPromise = Ember.typeOf(isFulfilled === 'boolean'); + // + // if (!modelIsAPromise) { + // this.set('model', this.get('formView.controller.model')); + // } + // } + // }), }); diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index 1ed3af1..484c9b5 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -9,6 +9,7 @@ var run = Ember.run; export default Ember.Component.extend({ as: null, layout: layout, + property: Ember.computed.oneWay('valueBinding._label'), showError: false, showValidity: false, value: null, @@ -28,12 +29,25 @@ export default Ember.Component.extend({ multiple: null, name: Ember.computed.oneWay('property'), placeholder: null, - property: Ember.computed.oneWay('valueBinding._label'), prompt: null, disabled: null, + fullPropertyPath: Ember.computed('parentView.modelPath', 'property', + function() { + var modelPath = defaultFor( + this.get('parentView.modelPath'), + 'model' + ); + + // return modelPath + '.' + this.get('property'); + return this.get('property'); + } + ), + label: Ember.computed('property', function() { - return toWords(defaultFor(this.get('property'), '')); + var property = defaultFor(this.get('property'), ''); + + return toWords(property.replace('model.', '')); }), type: Ember.computed('as', function() { diff --git a/addon/helpers/capitalize-string.js b/addon/helpers/capitalize-string.js new file mode 100644 index 0000000..aabd69b --- /dev/null +++ b/addon/helpers/capitalize-string.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; + +export function capitalizeString(params) { + var string = params[0]; + + return Ember.String.capitalize(string); +} + +export default Ember.HTMLBars.makeBoundHelper(capitalizeString); diff --git a/addon/helpers/to-words.js b/addon/helpers/to-words.js deleted file mode 100644 index f3cc7b4..0000000 --- a/addon/helpers/to-words.js +++ /dev/null @@ -1,9 +0,0 @@ -import Ember from 'ember'; - -export function toWords(params) { - var string = params[0]; - - return string.underscore().split('_').join(' ').capitalize(); -} - -export default Ember.HTMLBars.makeBoundHelper(toWords); diff --git a/app/templates/components/destroy-submission.hbs b/addon/templates/components/destroy-submission.hbs similarity index 100% rename from app/templates/components/destroy-submission.hbs rename to addon/templates/components/destroy-submission.hbs diff --git a/addon/templates/components/error-field.hbs b/addon/templates/components/error-field.hbs index 889d9ee..b10497a 100644 --- a/addon/templates/components/error-field.hbs +++ b/addon/templates/components/error-field.hbs @@ -1 +1,3 @@ -{{yield}} +{{#if error}} + {{capitalize-string label}} {{error}} +{{/if}} diff --git a/app/templates/components/form-controls.hbs b/addon/templates/components/form-controls.hbs similarity index 100% rename from app/templates/components/form-controls.hbs rename to addon/templates/components/form-controls.hbs diff --git a/app/templates/components/form-submission.hbs b/addon/templates/components/form-submission.hbs similarity index 100% rename from app/templates/components/form-submission.hbs rename to addon/templates/components/form-submission.hbs diff --git a/app/templates/components/form-wrapper.hbs b/addon/templates/components/form-wrapper.hbs similarity index 100% rename from app/templates/components/form-wrapper.hbs rename to addon/templates/components/form-wrapper.hbs diff --git a/addon/templates/components/input-wrapper.hbs b/addon/templates/components/input-wrapper.hbs index 60297ba..3ec172a 100644 --- a/addon/templates/components/input-wrapper.hbs +++ b/addon/templates/components/input-wrapper.hbs @@ -7,9 +7,16 @@ {{yield}} {{else}} {{!-- ...else show input --}} - {{input}} + {{input + placeholder=placeholder + name=name + type=type + value=value + }} {{/if}} -{{#if error}} - {{error-field property=property}} +{{error-field property=property label=label}} + +{{#if hint}} + {{hint-field hint=hint}} {{/if}} diff --git a/addon/templates/components/label-field.hbs b/addon/templates/components/label-field.hbs index 21f81c8..fc36da3 100644 --- a/addon/templates/components/label-field.hbs +++ b/addon/templates/components/label-field.hbs @@ -1 +1 @@ -{{label}} +{{capitalize-string label}} diff --git a/app/templates/components/loading-spinner.hbs b/addon/templates/components/loading-spinner.hbs similarity index 100% rename from app/templates/components/loading-spinner.hbs rename to addon/templates/components/loading-spinner.hbs diff --git a/addon/utils/to-words.js b/addon/utils/to-words.js index 927850e..2ceb5a5 100644 --- a/addon/utils/to-words.js +++ b/addon/utils/to-words.js @@ -1,5 +1,6 @@ -var String = Ember.String; - export default function(string) { - return String.capitalize(String.underscore(string).split('_').join(' ')); + var underscored = Ember.String.underscore(string); + var spaced = underscored.split('_').join(' '); + + return spaced.replace('.', ' '); } diff --git a/app/helpers/capitalize-string.js b/app/helpers/capitalize-string.js new file mode 100644 index 0000000..8ee0497 --- /dev/null +++ b/app/helpers/capitalize-string.js @@ -0,0 +1,3 @@ +import capitalizeString from 'ember-easy-form-extensions/helpers/capitalize-string'; + +export default capitalizeString; diff --git a/app/helpers/to-words.js b/app/helpers/to-words.js deleted file mode 100644 index cf6d4c7..0000000 --- a/app/helpers/to-words.js +++ /dev/null @@ -1,3 +0,0 @@ -import toWords from 'ember-easy-form-extensions/helpers/to-words'; - -export default toWords; diff --git a/app/templates/easy-form/error.hbs b/app/templates/easy-form/error.hbs deleted file mode 100644 index d5d3082..0000000 --- a/app/templates/easy-form/error.hbs +++ /dev/null @@ -1 +0,0 @@ -{{view.errorText}} diff --git a/app/templates/easy-form/hint.hbs b/app/templates/easy-form/hint.hbs deleted file mode 100644 index ba2337b..0000000 --- a/app/templates/easy-form/hint.hbs +++ /dev/null @@ -1 +0,0 @@ -{{view.hintText}} diff --git a/app/templates/easy-form/input-controls.hbs b/app/templates/easy-form/input-controls.hbs deleted file mode 100644 index 7b7d540..0000000 --- a/app/templates/easy-form/input-controls.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{input-field - propertyBinding='view.property' - inputOptionsBinding='view.inputOptionsValues' -}} - -{{#if view.showError}} - {{error-field propertyBinding='view.property'}} -{{/if}} - -{{#if view.hint}} - {{hint-field - propertyBinding='view.property' - textBinding='view.hint' - }} -{{/if}} diff --git a/app/templates/easy-form/input.hbs b/app/templates/easy-form/input.hbs deleted file mode 100644 index d222120..0000000 --- a/app/templates/easy-form/input.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{label-field propertyBinding='view.property' textBinding='view.label'}} - -
- {{partial 'easy-form/input-controls'}} -
diff --git a/app/templates/easy-form/label.hbs b/app/templates/easy-form/label.hbs deleted file mode 100644 index f87d1de..0000000 --- a/app/templates/easy-form/label.hbs +++ /dev/null @@ -1 +0,0 @@ -{{view.labelText}} diff --git a/tests/dummy/app/resources/post/edit/controller.js b/tests/dummy/app/resources/post/edit/controller.js index 3d4ec50..b4fc9bc 100644 --- a/tests/dummy/app/resources/post/edit/controller.js +++ b/tests/dummy/app/resources/post/edit/controller.js @@ -1,11 +1,11 @@ import Ember from 'ember'; import Saving from 'ember-easy-form-extensions/mixins/controllers/saving'; -export default Ember.ObjectController.extend( +export default Ember.Controller.extend( Saving, { validations: { - title: { + 'model.title': { presence: true } }, @@ -23,4 +23,3 @@ export default Ember.ObjectController.extend( } }); - diff --git a/tests/dummy/app/resources/posts/index/controller.js b/tests/dummy/app/resources/posts/index/controller.js index 602325d..b461f2c 100644 --- a/tests/dummy/app/resources/posts/index/controller.js +++ b/tests/dummy/app/resources/posts/index/controller.js @@ -1,5 +1,5 @@ import Ember from 'ember'; -export default Ember.ArrayController.extend({ +export default Ember.Controller.extend({ }); diff --git a/tests/dummy/app/resources/posts/index/template.hbs b/tests/dummy/app/resources/posts/index/template.hbs index 97fa19f..147c72c 100644 --- a/tests/dummy/app/resources/posts/index/template.hbs +++ b/tests/dummy/app/resources/posts/index/template.hbs @@ -1,4 +1,4 @@ -{{#each post in content}} +{{#each post in model}} {{#link-to 'post.edit' post}} {{post.title}} {{/link-to}} diff --git a/tests/dummy/app/resources/posts/new/controller.js b/tests/dummy/app/resources/posts/new/controller.js index c6054bc..8c0777b 100644 --- a/tests/dummy/app/resources/posts/new/controller.js +++ b/tests/dummy/app/resources/posts/new/controller.js @@ -1,11 +1,11 @@ import Ember from 'ember'; import Saving from 'ember-easy-form-extensions/mixins/controllers/saving'; -export default Ember.ObjectController.extend( +export default Ember.Controller.extend( Saving, { validations: { - title: { + 'model.title': { presence: true } }, @@ -19,4 +19,3 @@ export default Ember.ObjectController.extend( } }); - diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index ea24446..1ccf760 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -1,9 +1,9 @@ {{#form-wrapper}} {{#form-controls legend='Write a new post'}} - {{input-wrapper value=title}} + {{input-wrapper value=model.title}} {{!-- {{input-wrapper property=description}} - {{#input-wrapper property=title}} + {{#input-wrapper property='title'}} hey {{/input-wrapper}} --}} {{/form-controls}} @@ -14,11 +14,13 @@
Title
-
{{title}}
+
{{model.title}}
Description
-
{{description}}
+
{{model.description}}
+{{errors.model.title.firstObject}} + {{#if editing}} {{destroy-submission}} {{/if}} From 67ece81a32117dce73e8a6e9f6aa9800fd0a6115 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Tue, 12 May 2015 00:23:18 -0400 Subject: [PATCH 06/73] Basic label binding using id --- addon/components/input-wrapper.js | 4 ++++ addon/components/label-field.js | 2 ++ addon/templates/components/hint-field.hbs | 2 +- addon/templates/components/input-wrapper.hbs | 6 +++++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index 484c9b5..daaafcd 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -44,6 +44,10 @@ export default Ember.Component.extend({ } ), + inputId: Ember.computed(function() { + return this.get('elementId') + '-input'; + }), + label: Ember.computed('property', function() { var property = defaultFor(this.get('property'), ''); diff --git a/addon/components/label-field.js b/addon/components/label-field.js index 247c5fc..19c244d 100644 --- a/addon/components/label-field.js +++ b/addon/components/label-field.js @@ -2,7 +2,9 @@ import Ember from 'ember'; import layout from '../templates/components/label-field'; export default Ember.Component.extend({ + attributeBindings: ['for'], classNameBindings: ['easyForm.labelClass'], + for: null, label: null, layout: layout, tagName: 'label', diff --git a/addon/templates/components/hint-field.hbs b/addon/templates/components/hint-field.hbs index 889d9ee..818adfa 100644 --- a/addon/templates/components/hint-field.hbs +++ b/addon/templates/components/hint-field.hbs @@ -1 +1 @@ -{{yield}} +{{capitalize-string hint}} diff --git a/addon/templates/components/input-wrapper.hbs b/addon/templates/components/input-wrapper.hbs index 3ec172a..0c384e0 100644 --- a/addon/templates/components/input-wrapper.hbs +++ b/addon/templates/components/input-wrapper.hbs @@ -1,5 +1,8 @@ {{#if label}} - {{label-field label=label}} + {{label-field + for=inputId + label=label + }} {{/if}} {{#if template}} @@ -8,6 +11,7 @@ {{else}} {{!-- ...else show input --}} {{input + id=inputId placeholder=placeholder name=name type=type From 6045a55801e307d70ac0b72570fdc88ba9fff590 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Tue, 12 May 2015 17:49:38 -0400 Subject: [PATCH 07/73] Layouts added to components --- addon/components/destroy-submission.js | 2 ++ addon/components/form-controls.js | 2 ++ addon/components/form-submission.js | 2 ++ addon/components/form-wrapper.js | 2 ++ addon/components/loading-spinner.js | 4 +++- addon/templates/components/test-comp.hbs | 1 + app/components/test-comp.js | 3 +++ tests/unit/components/test-comp-test.js | 21 +++++++++++++++++++++ 8 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 addon/templates/components/test-comp.hbs create mode 100644 app/components/test-comp.js create mode 100644 tests/unit/components/test-comp-test.js diff --git a/addon/components/destroy-submission.js b/addon/components/destroy-submission.js index 48e4a80..6e7b516 100644 --- a/addon/components/destroy-submission.js +++ b/addon/components/destroy-submission.js @@ -1,4 +1,5 @@ import Ember from 'ember'; +import layout from '../templates/components/destroy-submission'; import WalkViews from 'ember-easy-form-extensions/mixins/views/walk-views'; export default Ember.Component.extend( @@ -8,6 +9,7 @@ export default Ember.Component.extend( destroyText: 'Delete', formSubmitted: Ember.computed.readOnly('formView.formSubmitted'), iconClass: 'icon-delete', + layout: layout, actions: { destroy: function() { diff --git a/addon/components/form-controls.js b/addon/components/form-controls.js index f80b543..de5cf8b 100644 --- a/addon/components/form-controls.js +++ b/addon/components/form-controls.js @@ -1,4 +1,5 @@ import Ember from 'ember'; +import layout from '../templates/components/form-controls'; import softAssert from '../utils/observers/soft-assert'; import WalkViews from '../mixins/views/walk-views'; @@ -7,6 +8,7 @@ export default Ember.Component.extend( attributeBindings: ['legend'], classNameBindings: ['easyForm.formControlsClass'], + layout: layout, legend: null, model: null, modelPath: Ember.computed.oneWay('modelBinding._label'), diff --git a/addon/components/form-submission.js b/addon/components/form-submission.js index a1a8c92..06d1249 100644 --- a/addon/components/form-submission.js +++ b/addon/components/form-submission.js @@ -1,4 +1,5 @@ import Ember from 'ember'; +import layout from '../templates/components/form-submission'; import WalkViews from 'ember-easy-form-extensions/mixins/views/walk-views'; export default Ember.Component.extend( @@ -8,6 +9,7 @@ export default Ember.Component.extend( cancelText: 'Cancel', classNames: ['buttons', 'submission'], formSubmitted: Ember.computed.readOnly('formView.formSubmitted'), + layout: layout, submit: true, submitText: 'Save', diff --git a/addon/components/form-wrapper.js b/addon/components/form-wrapper.js index 824a90d..22f3835 100644 --- a/addon/components/form-wrapper.js +++ b/addon/components/form-wrapper.js @@ -1,9 +1,11 @@ import Ember from 'ember'; +import layout from '../templates/components/form-wrapper'; export default Ember.Component.extend({ attributeBindings: ['novalidate'], classNameBindings: ['easyForm.formWrapperClass'], className: 'form', + layout: layout, novalidate: true, tagName: 'form', }); diff --git a/addon/components/loading-spinner.js b/addon/components/loading-spinner.js index 70afc9c..7d9d965 100644 --- a/addon/components/loading-spinner.js +++ b/addon/components/loading-spinner.js @@ -1,5 +1,7 @@ import Ember from 'ember'; +import layout from '../templates/components/loading-spinner'; export default Ember.Component.extend({ - classNames: ['spinner'] + classNames: ['spinner'], + layout: layout }); diff --git a/addon/templates/components/test-comp.hbs b/addon/templates/components/test-comp.hbs new file mode 100644 index 0000000..889d9ee --- /dev/null +++ b/addon/templates/components/test-comp.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/app/components/test-comp.js b/app/components/test-comp.js new file mode 100644 index 0000000..dc7166a --- /dev/null +++ b/app/components/test-comp.js @@ -0,0 +1,3 @@ +import testComp from 'ember-easy-form-extensions/components/test-comp'; + +export default testComp; diff --git a/tests/unit/components/test-comp-test.js b/tests/unit/components/test-comp-test.js new file mode 100644 index 0000000..a89d846 --- /dev/null +++ b/tests/unit/components/test-comp-test.js @@ -0,0 +1,21 @@ +import { + moduleForComponent, + test +} from 'ember-qunit'; + +moduleForComponent('test-comp', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'] +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); From 1b79080ce8514949d54a81e51196929e98b30dac Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Tue, 12 May 2015 21:57:37 -0400 Subject: [PATCH 08/73] Clean path property and model path property refactored --- addon/components/form-controls.js | 7 +++++++ addon/components/input-wrapper.js | 15 +++++---------- tests/dummy/app/resources/posts/new/template.hbs | 6 +++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/addon/components/form-controls.js b/addon/components/form-controls.js index de5cf8b..1d2783f 100644 --- a/addon/components/form-controls.js +++ b/addon/components/form-controls.js @@ -1,3 +1,4 @@ +import defaultFor from '../utils/default-for'; import Ember from 'ember'; import layout from '../templates/components/form-controls'; import softAssert from '../utils/observers/soft-assert'; @@ -15,6 +16,12 @@ export default Ember.Component.extend( tagName: 'fieldset', checkForLegend: softAssert('legend'), + modelPath: Ember.computed('modelBinding._label', function() { + var modelPath = this.get('modelBinding._label'); + + return defaultFor(modelPath, 'model') + '.'; + }), + // findDefaultModel: Ember.on('init', function() { // var isFulfilled, modelIsAPromise; // diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index daaafcd..2d6daac 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -9,6 +9,7 @@ var run = Ember.run; export default Ember.Component.extend({ as: null, layout: layout, + modelPath: Ember.computed.oneWay('parentView.modelPath'), property: Ember.computed.oneWay('valueBinding._label'), showError: false, showValidity: false, @@ -32,15 +33,9 @@ export default Ember.Component.extend({ prompt: null, disabled: null, - fullPropertyPath: Ember.computed('parentView.modelPath', 'property', + cleanProperty: Ember.computed('property', 'modelPath', function() { - var modelPath = defaultFor( - this.get('parentView.modelPath'), - 'model' - ); - - // return modelPath + '.' + this.get('property'); - return this.get('property'); + return this.get('property').replace(this.get('modelPath')); } ), @@ -51,12 +46,12 @@ export default Ember.Component.extend({ label: Ember.computed('property', function() { var property = defaultFor(this.get('property'), ''); - return toWords(property.replace('model.', '')); + return toWords(property.replace(this.get('modelPath'), '')); }), type: Ember.computed('as', function() { var as = this.get('as'); - var property = this.get('property'); + var property = this.get('cleanProperty'); var type, value; if (!as) { diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index 1ccf760..940f9a7 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -1,11 +1,11 @@ {{#form-wrapper}} {{#form-controls legend='Write a new post'}} - {{input-wrapper value=model.title}} - {{!-- {{input-wrapper property=description}} + {{input-wrapper value=model.title placeholder='Title'}} + {{input-wrapper value=description}} {{#input-wrapper property='title'}} hey - {{/input-wrapper}} --}} + {{/input-wrapper}} {{/form-controls}} {{form-submission}} From 58223bad816ba94d783c3466191ea7f94826fdd4 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Tue, 12 May 2015 22:44:51 -0400 Subject: [PATCH 09/73] Basic partial setup for input --- addon/components/form-controls.js | 14 ---- addon/components/input-wrapper.js | 67 +++++++++---------- addon/services/easy-form.js | 1 - addon/templates/components/input-wrapper.hbs | 8 +-- app/templates/form-inputs/default.hbs | 7 ++ .../app/resources/posts/new/template.hbs | 2 +- 6 files changed, 40 insertions(+), 59 deletions(-) create mode 100644 app/templates/form-inputs/default.hbs diff --git a/addon/components/form-controls.js b/addon/components/form-controls.js index 1d2783f..5edd647 100644 --- a/addon/components/form-controls.js +++ b/addon/components/form-controls.js @@ -12,7 +12,6 @@ export default Ember.Component.extend( layout: layout, legend: null, model: null, - modelPath: Ember.computed.oneWay('modelBinding._label'), tagName: 'fieldset', checkForLegend: softAssert('legend'), @@ -21,17 +20,4 @@ export default Ember.Component.extend( return defaultFor(modelPath, 'model') + '.'; }), - - // findDefaultModel: Ember.on('init', function() { - // var isFulfilled, modelIsAPromise; - // - // if (!this.get('model')) { - // isFulfilled = this.get('model.isFulfilled'); - // modelIsAPromise = Ember.typeOf(isFulfilled === 'boolean'); - // - // if (!modelIsAPromise) { - // this.set('model', this.get('formView.controller.model')); - // } - // } - // }), }); diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index 2d6daac..55393b0 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -7,7 +7,7 @@ var typeOf = Ember.typeOf; var run = Ember.run; export default Ember.Component.extend({ - as: null, + inputPartial: 'form-inputs/default', layout: layout, modelPath: Ember.computed.oneWay('parentView.modelPath'), property: Ember.computed.oneWay('valueBinding._label'), @@ -35,57 +35,52 @@ export default Ember.Component.extend({ cleanProperty: Ember.computed('property', 'modelPath', function() { - return this.get('property').replace(this.get('modelPath')); + return this.get('property').replace(this.get('modelPath'), ''); } ), + formInputPartial: Ember.computed('type', function() { + var directory = this.get('easyForm.formInputsDirectory'); + + return directory + '/' + this.get('type'); + }), + inputId: Ember.computed(function() { return this.get('elementId') + '-input'; }), label: Ember.computed('property', function() { - var property = defaultFor(this.get('property'), ''); + var property = defaultFor(this.get('cleanProperty'), ''); - return toWords(property.replace(this.get('modelPath'), '')); + return toWords(property); }), - type: Ember.computed('as', function() { - var as = this.get('as'); + type: Ember.computed(function() { var property = this.get('cleanProperty'); var type, value; - if (!as) { - if (property.match(/password/)) { - type = 'password'; - } else if (property.match(/email/)) { - type = 'email'; - } else if (property.match(/url/)) { - type = 'url'; - } else if (property.match(/color/)) { - type = 'color'; - } else if (property.match(/^tel/) || property.match(/^phone/)) { - type = 'tel'; - } else if (property.match(/search/)) { - type = 'search'; - } else { - value = this.get('value'); - - if (typeOf(value) === 'number') { - type = 'number'; - } else if (typeOf(value) === 'date') { - type = 'date'; - } else if (typeOf(value) === 'boolean') { - type = 'checkbox'; - } - } + if (property.match(/password/)) { + type = 'password'; + } else if (property.match(/email/)) { + type = 'email'; + } else if (property.match(/url/)) { + type = 'url'; + } else if (property.match(/color/)) { + type = 'color'; + } else if (property.match(/^tel/) || property.match(/^phone/)) { + type = 'tel'; + } else if (property.match(/search/)) { + type = 'search'; } else { - var inputType = this.get('easyForm.inputTypes.' + property); - - if (inputType) { - type = inputType; + value = this.get('value'); + + if (typeOf(value) === 'number') { + type = 'number'; + } else if (typeOf(value) === 'date') { + type = 'date'; + } else if (typeOf(value) === 'boolean') { + type = 'checkbox'; } - - type = as; } return type; diff --git a/addon/services/easy-form.js b/addon/services/easy-form.js index 9905f8b..2f468d1 100644 --- a/addon/services/easy-form.js +++ b/addon/services/easy-form.js @@ -1,7 +1,6 @@ import Ember from 'ember'; export default Ember.Service.extend({ - inputTypes: {}, errorClass: 'error', hintClass: 'hint', inputClass: 'input', diff --git a/addon/templates/components/input-wrapper.hbs b/addon/templates/components/input-wrapper.hbs index 0c384e0..ddf592a 100644 --- a/addon/templates/components/input-wrapper.hbs +++ b/addon/templates/components/input-wrapper.hbs @@ -10,13 +10,7 @@ {{yield}} {{else}} {{!-- ...else show input --}} - {{input - id=inputId - placeholder=placeholder - name=name - type=type - value=value - }} + {{partial inputPartial}} {{/if}} {{error-field property=property label=label}} diff --git a/app/templates/form-inputs/default.hbs b/app/templates/form-inputs/default.hbs new file mode 100644 index 0000000..588716d --- /dev/null +++ b/app/templates/form-inputs/default.hbs @@ -0,0 +1,7 @@ +{{input + id=inputId + placeholder=placeholder + name=name + type=type + value=value +}} diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index 940f9a7..2765948 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -2,7 +2,7 @@ {{#form-controls legend='Write a new post'}} {{input-wrapper value=model.title placeholder='Title'}} - {{input-wrapper value=description}} + {{input-wrapper value=model.description}} {{#input-wrapper property='title'}} hey {{/input-wrapper}} From 36825d33c1b556ab65306cc9b7a18cde098bc49f Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Tue, 12 May 2015 23:40:48 -0400 Subject: [PATCH 10/73] Validations shown on form submisison --- addon/components/error-field.js | 16 ++++-- addon/components/input-wrapper.js | 57 ++++++-------------- addon/mixins/views/submitting.js | 16 ++---- addon/templates/components/input-wrapper.hbs | 6 ++- addon/templates/form-inputs/default.hbs | 7 +++ app/templates/form-inputs/default.hbs | 2 + 6 files changed, 44 insertions(+), 60 deletions(-) create mode 100644 addon/templates/form-inputs/default.hbs diff --git a/addon/components/error-field.js b/addon/components/error-field.js index 3e09a30..14d6262 100644 --- a/addon/components/error-field.js +++ b/addon/components/error-field.js @@ -7,13 +7,20 @@ import WalkViews from '../mixins/views/walk-views'; export default Ember.Component.extend( WalkViews, { - classNameBindings: ['easyForm.errorClass'], + shouldShowError: true, + classNameBindings: ['easyForm.errorClass', 'errorIsVisible:visible'], error: null, label: Ember.computed.oneWay('property'), layout: layout, property: null, tagName: 'span', + errorIsVisible: Ember.computed('shouldShowError', 'error', + function() { + return this.get('shouldShowError') && !!this.get('error'); + } + ), + text: Ember.computed('errors.[]', 'value', function() { var propertyName = defaultFor( this.get('property'), @@ -24,13 +31,12 @@ export default Ember.Component.extend( }), addErrorObserver: Ember.on('init', function() { - var fullPropertyPath = this.get('parentView.fullPropertyPath'); + var property = this.get('property'); var controller, errorPath, setError; - if (fullPropertyPath) { + if (property) { controller = this.get('formView.controller'); - - errorPath = 'errors.' + fullPropertyPath + '.firstObject'; + errorPath = 'errors.' + property + '.firstObject'; setError = function() { this.set('error', controller.get(errorPath)); diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index 55393b0..6a6c819 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -2,17 +2,19 @@ import defaultFor from '../utils/default-for'; import Ember from 'ember'; import layout from '../templates/components/input-wrapper'; import toWords from '../utils/to-words'; +import WalkViews from '../mixins/views/walk-views'; var typeOf = Ember.typeOf; var run = Ember.run; -export default Ember.Component.extend({ +export default Ember.Component.extend( + WalkViews, { + inputPartial: 'form-inputs/default', layout: layout, modelPath: Ember.computed.oneWay('parentView.modelPath'), property: Ember.computed.oneWay('valueBinding._label'), - showError: false, - showValidity: false, + shouldShowError: false, value: null, classNameBindings: [ @@ -86,45 +88,16 @@ export default Ember.Component.extend({ return type; }), - setInvalidToValid: Ember.observer('showError', function() { - // If we go from error to no error - if (!this.get('showError') && this.get('canShowValidationError')) { - run.debounce(this, function() { - var hasAnError = this.get('formForModel.errors.' + this.get('property') + '.length'); - - if (!hasAnError && !this.get('isDestroying')) { - this.set('showValidity', true); - - run.later(this, function() { - if (!this.get('isDestroying')) { - this.set('showValidity', false); - } - }, 2000); - } - }, 50); - } - }), - - /** - An override of easyForm's default `focusOut` method to ensure validations are not shown when the user clicks cancel. - - @method focusOut - */ - - focusOut: function() { - - /* Hacky - delay check so focusOut runs after the cancel action */ - - run.later(this, function() { - var cancelClicked = this.get('parentView.cancelClicked'); - var isDestroying = this.get('isDestroying'); - - if (!cancelClicked && !isDestroying) { - this.set('hasFocusedOut', true); - this.showValidationError(); - } - }, 100); + actions: { + showError: function() { + this.set('shouldShowError', true); + }, }, - showValidationError: Ember.K + listenForSubmit: Ember.on('init', function() { + this.get('formView').on('submission', function() { + this.send('showError'); + }.bind(this)); + }), + }); diff --git a/addon/mixins/views/submitting.js b/addon/mixins/views/submitting.js index 6c90c77..d0b7fae 100644 --- a/addon/mixins/views/submitting.js +++ b/addon/mixins/views/submitting.js @@ -1,6 +1,8 @@ import Ember from 'ember'; -export default Ember.Mixin.create({ +export default Ember.Mixin.create( + Ember.Evented, { + cancelClicked: false, formSubmitted: Ember.computed.alias('controller.formSubmitted'), @@ -40,17 +42,7 @@ export default Ember.Mixin.create({ event.stopPropagation(); this.set('formSubmitted', true); - - this.get('childViews').forEach(function(view) { - var viewConstructor = view.get('constructor').toString(); - - /* If the view is an Easy Form input, manually call focus - out to show the validation error */ - - if (viewConstructor === 'Ember.EasyForm.Input') { - view.focusOut(); - } - }); + this.trigger('submission'); this._eventHandler('submit'); }, diff --git a/addon/templates/components/input-wrapper.hbs b/addon/templates/components/input-wrapper.hbs index ddf592a..38cc9a9 100644 --- a/addon/templates/components/input-wrapper.hbs +++ b/addon/templates/components/input-wrapper.hbs @@ -13,7 +13,11 @@ {{partial inputPartial}} {{/if}} -{{error-field property=property label=label}} +{{error-field + shouldShowError=shouldShowError + label=label + property=property +}} {{#if hint}} {{hint-field hint=hint}} diff --git a/addon/templates/form-inputs/default.hbs b/addon/templates/form-inputs/default.hbs new file mode 100644 index 0000000..588716d --- /dev/null +++ b/addon/templates/form-inputs/default.hbs @@ -0,0 +1,7 @@ +{{input + id=inputId + placeholder=placeholder + name=name + type=type + value=value +}} diff --git a/app/templates/form-inputs/default.hbs b/app/templates/form-inputs/default.hbs index 588716d..1e0cb55 100644 --- a/app/templates/form-inputs/default.hbs +++ b/app/templates/form-inputs/default.hbs @@ -4,4 +4,6 @@ name=name type=type value=value + action='showError' + on='focus-out' }} From 8a4b8047e290c401b9d74417debd70e3546f4b43 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Wed, 13 May 2015 00:02:30 -0400 Subject: [PATCH 11/73] Is newly valid class name binding --- addon/components/error-field.js | 7 ++++- addon/components/input-wrapper.js | 33 ++++++++++++++++++-- addon/templates/components/input-wrapper.hbs | 2 ++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/addon/components/error-field.js b/addon/components/error-field.js index 14d6262..22b5ce8 100644 --- a/addon/components/error-field.js +++ b/addon/components/error-field.js @@ -20,7 +20,7 @@ export default Ember.Component.extend( return this.get('shouldShowError') && !!this.get('error'); } ), - + text: Ember.computed('errors.[]', 'value', function() { var propertyName = defaultFor( this.get('property'), @@ -39,6 +39,11 @@ export default Ember.Component.extend( errorPath = 'errors.' + property + '.firstObject'; setError = function() { + var error = controller.get(errorPath); + var parentView = this.get('parentView'); + var isValid = !error && parentView.get('isInputWrapper'); + + parentView.set('isValid', isValid); this.set('error', controller.get(errorPath)); }; diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index 6a6c819..206da50 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -11,15 +11,17 @@ export default Ember.Component.extend( WalkViews, { inputPartial: 'form-inputs/default', + isInputWrapper: true, // Static + isNewlyValid: false, + isValid: true, layout: layout, modelPath: Ember.computed.oneWay('parentView.modelPath'), property: Ember.computed.oneWay('valueBinding._label'), shouldShowError: false, - value: null, classNameBindings: [ 'easyForm.inputWrapperClass', - 'showValidity:control-valid' + 'validityClass' ], /* Input attributes */ @@ -88,12 +90,39 @@ export default Ember.Component.extend( return type; }), + validityClass: Ember.computed('easyForm.inputWrapperClass', 'isNewlyValid', 'isValid', + function() { + var baseClass = this.get('easyForm.inputWrapperClass'); + var modifier; + + if (this.get('isNewlyValid')) { + modifier = 'newly-valid'; + } else if(this.get('isValid')) { + modifier = 'valid'; + } else { + modifier = 'error'; + } + + return baseClass + '-' + modifier; + } + ), + actions: { showError: function() { this.set('shouldShowError', true); }, }, + listenForNewlyValid: Ember.observer('isValid', function() { + if (this.get('isValid')) { + this.set('isNewlyValid', true); + } + + Ember.run.later(this, function() { + this.set('isNewlyValid', false); + }, 3000); + }), + listenForSubmit: Ember.on('init', function() { this.get('formView').on('submission', function() { this.send('showError'); diff --git a/addon/templates/components/input-wrapper.hbs b/addon/templates/components/input-wrapper.hbs index 38cc9a9..1ede6c4 100644 --- a/addon/templates/components/input-wrapper.hbs +++ b/addon/templates/components/input-wrapper.hbs @@ -1,3 +1,5 @@ +.{{isNewlyValid}}. + {{#if label}} {{label-field for=inputId From a8177a4246efe4e7461e072ab15698e42f29afc5 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Wed, 13 May 2015 00:09:00 -0400 Subject: [PATCH 12/73] Controller fix --- addon/initializers/routing-events.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addon/initializers/routing-events.js b/addon/initializers/routing-events.js index 6d82589..f9d9e83 100644 --- a/addon/initializers/routing-events.js +++ b/addon/initializers/routing-events.js @@ -3,11 +3,11 @@ import Ember from 'ember'; export function initialize(/* container, app */) { /** - @class Ember.ControllerMixin + @class Ember.Controller @submodule controllers */ - Ember.ControllerMixin.reopen( + Ember.Controller.reopen( Ember.Evented, { }); From 3219bd1693aa23b64bdf7a096e3473072f311812 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Wed, 13 May 2015 21:55:41 -0400 Subject: [PATCH 13/73] Select added --- addon/components/input-wrapper.js | 14 +++++++------- addon/services/easy-form.js | 6 +++++- addon/templates/form-inputs/default.hbs | 7 ------- app/templates/form-inputs/select.hbs | 7 +++++++ tests/dummy/app/resources/post/model.js | 6 ++++-- tests/dummy/app/resources/posts/new/controller.js | 5 +++++ tests/dummy/app/resources/posts/new/template.hbs | 8 +++++--- 7 files changed, 33 insertions(+), 20 deletions(-) delete mode 100644 addon/templates/form-inputs/default.hbs create mode 100644 app/templates/form-inputs/select.hbs diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index 206da50..aba49ee 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -10,7 +10,6 @@ var run = Ember.run; export default Ember.Component.extend( WalkViews, { - inputPartial: 'form-inputs/default', isInputWrapper: true, // Static isNewlyValid: false, isValid: true, @@ -43,16 +42,17 @@ export default Ember.Component.extend( } ), - formInputPartial: Ember.computed('type', function() { - var directory = this.get('easyForm.formInputsDirectory'); - - return directory + '/' + this.get('type'); - }), - inputId: Ember.computed(function() { return this.get('elementId') + '-input'; }), + inputPartial: Ember.computed('type', function() { + var type = this.get('type'); + var partialName = this.get('easyForm.inputTypePartials.' + type); + + return defaultFor(partialName, 'form-inputs/default'); + }), + label: Ember.computed('property', function() { var property = defaultFor(this.get('cleanProperty'), ''); diff --git a/addon/services/easy-form.js b/addon/services/easy-form.js index 2f468d1..ccec365 100644 --- a/addon/services/easy-form.js +++ b/addon/services/easy-form.js @@ -6,5 +6,9 @@ export default Ember.Service.extend({ inputClass: 'input', inputWrapperClass: 'input-wrapper', formWrapperClass: 'form', - formControlsClass: 'controls' + formControlsClass: 'controls', + + inputTypePartials: { + select: 'form-inputs/select' + } }); diff --git a/addon/templates/form-inputs/default.hbs b/addon/templates/form-inputs/default.hbs deleted file mode 100644 index 588716d..0000000 --- a/addon/templates/form-inputs/default.hbs +++ /dev/null @@ -1,7 +0,0 @@ -{{input - id=inputId - placeholder=placeholder - name=name - type=type - value=value -}} diff --git a/app/templates/form-inputs/select.hbs b/app/templates/form-inputs/select.hbs new file mode 100644 index 0000000..912b332 --- /dev/null +++ b/app/templates/form-inputs/select.hbs @@ -0,0 +1,7 @@ +{{view 'select' + id=inputId + content=content + name=name + value=value + selection=selection +}} diff --git a/tests/dummy/app/resources/post/model.js b/tests/dummy/app/resources/post/model.js index 671dcb7..b5b8996 100644 --- a/tests/dummy/app/resources/post/model.js +++ b/tests/dummy/app/resources/post/model.js @@ -5,7 +5,8 @@ var attr = DS.attr; var PostModel = DS.Model.extend({ title: attr('string'), - description: attr('string') + description: attr('string'), + category: attr('string'), }); PostModel.reopenClass({ @@ -13,7 +14,8 @@ PostModel.reopenClass({ { id: 1, title: 'How to Ember', - description: 'This is an introduction on how to Ember. Wow.' + description: 'This is an introduction on how to Ember. Wow.', + category: 'ember' } ] }); diff --git a/tests/dummy/app/resources/posts/new/controller.js b/tests/dummy/app/resources/posts/new/controller.js index 8c0777b..f72989b 100644 --- a/tests/dummy/app/resources/posts/new/controller.js +++ b/tests/dummy/app/resources/posts/new/controller.js @@ -4,9 +4,14 @@ import Saving from 'ember-easy-form-extensions/mixins/controllers/saving'; export default Ember.Controller.extend( Saving, { + categories: Ember.A(['ember', 'rails', 'css']), + validations: { 'model.title': { presence: true + }, + 'model.category': { + presence: true } }, diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index 2765948..cff9864 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -3,9 +3,11 @@ {{#form-controls legend='Write a new post'}} {{input-wrapper value=model.title placeholder='Title'}} {{input-wrapper value=model.description}} - {{#input-wrapper property='title'}} - hey - {{/input-wrapper}} + {{input-wrapper + value=model.category + content=categories + type='select' + }} {{/form-controls}} {{form-submission}} From 6c091ef61874d53b7394f54b1cffb3745f07d217 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Wed, 13 May 2015 22:13:15 -0400 Subject: [PATCH 14/73] Added textarea --- addon/initializers/easy-form-extensions.js | 2 -- addon/services/easy-form.js | 3 ++- app/templates/form-inputs/default.hbs | 3 +-- app/templates/form-inputs/textarea.hbs | 8 ++++++++ tests/dummy/app/resources/posts/new/controller.js | 3 +++ tests/dummy/app/resources/posts/new/template.hbs | 2 +- 6 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 app/templates/form-inputs/textarea.hbs diff --git a/addon/initializers/easy-form-extensions.js b/addon/initializers/easy-form-extensions.js index bf8ca59..90a3107 100644 --- a/addon/initializers/easy-form-extensions.js +++ b/addon/initializers/easy-form-extensions.js @@ -1,8 +1,6 @@ import Ember from 'ember'; export function initialize(container, app) { - var run = Ember.run; - app.inject('component', 'easyForm', 'service:easy-form'); } diff --git a/addon/services/easy-form.js b/addon/services/easy-form.js index ccec365..3a0d3fb 100644 --- a/addon/services/easy-form.js +++ b/addon/services/easy-form.js @@ -9,6 +9,7 @@ export default Ember.Service.extend({ formControlsClass: 'controls', inputTypePartials: { - select: 'form-inputs/select' + select: 'form-inputs/select', + textarea: 'form-inputs/textarea' } }); diff --git a/app/templates/form-inputs/default.hbs b/app/templates/form-inputs/default.hbs index 1e0cb55..a59dfb8 100644 --- a/app/templates/form-inputs/default.hbs +++ b/app/templates/form-inputs/default.hbs @@ -4,6 +4,5 @@ name=name type=type value=value - action='showError' - on='focus-out' + focus-out='showError' }} diff --git a/app/templates/form-inputs/textarea.hbs b/app/templates/form-inputs/textarea.hbs new file mode 100644 index 0000000..b66ff6f --- /dev/null +++ b/app/templates/form-inputs/textarea.hbs @@ -0,0 +1,8 @@ +{{textarea + id=inputId + placeholder=placeholder + name=name + type=type + value=value + focus-out='showError' +}} diff --git a/tests/dummy/app/resources/posts/new/controller.js b/tests/dummy/app/resources/posts/new/controller.js index f72989b..9253fd5 100644 --- a/tests/dummy/app/resources/posts/new/controller.js +++ b/tests/dummy/app/resources/posts/new/controller.js @@ -10,6 +10,9 @@ export default Ember.Controller.extend( 'model.title': { presence: true }, + 'model.description': { + presence: true + }, 'model.category': { presence: true } diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index cff9864..f916a21 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -2,7 +2,7 @@ {{#form-controls legend='Write a new post'}} {{input-wrapper value=model.title placeholder='Title'}} - {{input-wrapper value=model.description}} + {{input-wrapper value=model.description type='textarea'}} {{input-wrapper value=model.category content=categories From 853b0242593e2e6de955a4c8e86b51440a370d64 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Wed, 13 May 2015 22:46:17 -0400 Subject: [PATCH 15/73] Checkbox added --- addon/services/easy-form.js | 3 ++- app/templates/form-inputs/checkbox.hbs | 8 ++++++++ tests/dummy/app/resources/post/model.js | 2 ++ tests/dummy/app/resources/posts/new/template.hbs | 8 ++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 app/templates/form-inputs/checkbox.hbs diff --git a/addon/services/easy-form.js b/addon/services/easy-form.js index 3a0d3fb..bd3c3bf 100644 --- a/addon/services/easy-form.js +++ b/addon/services/easy-form.js @@ -9,7 +9,8 @@ export default Ember.Service.extend({ formControlsClass: 'controls', inputTypePartials: { + checkbox: 'form-inputs/checkbox', select: 'form-inputs/select', - textarea: 'form-inputs/textarea' + textarea: 'form-inputs/textarea', } }); diff --git a/app/templates/form-inputs/checkbox.hbs b/app/templates/form-inputs/checkbox.hbs new file mode 100644 index 0000000..9ffe55c --- /dev/null +++ b/app/templates/form-inputs/checkbox.hbs @@ -0,0 +1,8 @@ +{{input + id=inputId + placeholder=placeholder + name=name + type=type + checked=value + focus-out='showError' +}} diff --git a/tests/dummy/app/resources/post/model.js b/tests/dummy/app/resources/post/model.js index b5b8996..bb47fe7 100644 --- a/tests/dummy/app/resources/post/model.js +++ b/tests/dummy/app/resources/post/model.js @@ -7,6 +7,8 @@ var PostModel = DS.Model.extend({ title: attr('string'), description: attr('string'), category: attr('string'), + views: attr('number'), + published: attr('boolean') }); PostModel.reopenClass({ diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index f916a21..920b83e 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -8,6 +8,14 @@ content=categories type='select' }} + {{input-wrapper + type='number' + value=model.views + }} + {{input-wrapper + type='checkbox' + value=model.published + }} {{/form-controls}} {{form-submission}} From 04cdb7643ac4502afaf9a3622bb421cf060c4a0a Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sun, 17 May 2015 16:05:25 -0400 Subject: [PATCH 16/73] dirty record handler added and dummy app updated --- addon/components/input-wrapper.js | 3 ++ addon/mixins/routes/delete-record.js | 13 ------- addon/mixins/routes/dirty-record-handler.js | 37 ++++++++++++++++++ addon/mixins/routes/rollback.js | 13 ------- addon/templates/components/input-wrapper.hbs | 2 - bower.json | 5 ++- index.js | 2 +- package.json | 3 +- tests/.jshintrc | 3 +- tests/dummy/app/resources/post/edit/route.js | 4 +- tests/dummy/app/resources/post/index/route.js | 2 +- tests/dummy/app/resources/post/model.js | 22 ++++++++--- tests/dummy/app/resources/posts/new/route.js | 4 +- .../app/resources/posts/new/template.hbs | 39 ++++++++++++++----- 14 files changed, 100 insertions(+), 52 deletions(-) delete mode 100644 addon/mixins/routes/delete-record.js create mode 100644 addon/mixins/routes/dirty-record-handler.js delete mode 100644 addon/mixins/routes/rollback.js diff --git a/addon/components/input-wrapper.js b/addon/components/input-wrapper.js index aba49ee..8b3aecb 100644 --- a/addon/components/input-wrapper.js +++ b/addon/components/input-wrapper.js @@ -26,6 +26,7 @@ export default Ember.Component.extend( /* Input attributes */ collection: null, + content: null, optionValuePath: null, optionLabelPath: null, selection: null, @@ -75,6 +76,8 @@ export default Ember.Component.extend( type = 'tel'; } else if (property.match(/search/)) { type = 'search'; + } else if (this.get('content')) { + type = 'select'; } else { value = this.get('value'); diff --git a/addon/mixins/routes/delete-record.js b/addon/mixins/routes/delete-record.js deleted file mode 100644 index dd850bf..0000000 --- a/addon/mixins/routes/delete-record.js +++ /dev/null @@ -1,13 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Mixin.create({ - - deleteRecord: Ember.on('willTransition', function() { - var model = this.get('controller.content'); - - if (model.get('isDirty')) { - model.deleteRecord(); - } - }), - -}); diff --git a/addon/mixins/routes/dirty-record-handler.js b/addon/mixins/routes/dirty-record-handler.js new file mode 100644 index 0000000..d401116 --- /dev/null +++ b/addon/mixins/routes/dirty-record-handler.js @@ -0,0 +1,37 @@ +/** +Undo changes in the store made to an existing but not-saved +model. This should be mixed into 'edit' routes like +`CampaignEditRoute` and `BusinessEditRoute`. All non-persisted +changes to the model are undone. + +@class DireyRecordHandler +@submodule mixins +*/ + +import Ember from 'ember'; +import defaultFor from 'ember-easy-form-extensions/utils/default-for'; + +export default Ember.Mixin.create({ + + /** + If the model `isDirty` (i.e. some data has been temporarily + changed) rollback the record to the most recent clean version + in the store. If there is no clean version in the store, + delete the record. + + @method rollbackifDirty + */ + + rollbackIfDirty: Ember.on('willTransition', function(model) { + model = defaultFor(model, this.get('controller.model')); + + if (model.get('isDirty')) { + if (model.get('id')) { + model.rollback(); + } else { + model.deleteRecord(); + } + } + }), + +}); diff --git a/addon/mixins/routes/rollback.js b/addon/mixins/routes/rollback.js deleted file mode 100644 index 7d1f050..0000000 --- a/addon/mixins/routes/rollback.js +++ /dev/null @@ -1,13 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Mixin.create({ - - rollback: Ember.on('willTransition', function() { - var model = this.get('controller.content'); - - if (model.get('isDirty')) { - model.rollback(); - } - }), - -}); diff --git a/addon/templates/components/input-wrapper.hbs b/addon/templates/components/input-wrapper.hbs index 1ede6c4..38cc9a9 100644 --- a/addon/templates/components/input-wrapper.hbs +++ b/addon/templates/components/input-wrapper.hbs @@ -1,5 +1,3 @@ -.{{isNewlyValid}}. - {{#if label}} {{label-field for=inputId diff --git a/bower.json b/bower.json index e1a7ec3..425d5db 100644 --- a/bower.json +++ b/bower.json @@ -11,6 +11,7 @@ "ember-resolver": "~0.1.15", "jquery": "^1.11.1", "loader.js": "ember-cli/loader.js#3.2.0", - "qunit": "~1.17.1" + "qunit": "~1.17.1", + "ember-cli-moment-shim": "~0.1.0" } -} \ No newline at end of file +} diff --git a/index.js b/index.js index d5dcd1b..88ae01a 100644 --- a/index.js +++ b/index.js @@ -2,5 +2,5 @@ 'use strict'; module.exports = { - name: 'ember-easy-form-extensions', + name: 'ember-easy-form-extensions' }; diff --git a/package.json b/package.json index 6621836..18f6ce6 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ }, "devDependencies": { "broccoli-asset-rev": "^2.0.2", - "ember-cli": "0.2.3", + "ember-cli": "0.2.5", "ember-cli-app-version": "0.3.3", "ember-cli-content-security-policy": "0.4.0", "ember-cli-dependency-checker": "0.0.8", @@ -38,6 +38,7 @@ "ember-data": "1.0.0-beta.16.1", "ember-disable-prototype-extensions": "^1.0.0", "ember-export-application-global": "^1.0.2", + "ember-moment": "1.1.1", "ember-try": "0.0.4" }, "keywords": [ diff --git a/tests/.jshintrc b/tests/.jshintrc index ea8b88f..a197e1a 100644 --- a/tests/.jshintrc +++ b/tests/.jshintrc @@ -21,7 +21,8 @@ "andThen", "currentURL", "currentPath", - "currentRouteName" + "currentRouteName", + "moment" ], "node": false, "browser": false, diff --git a/tests/dummy/app/resources/post/edit/route.js b/tests/dummy/app/resources/post/edit/route.js index da12612..d282361 100644 --- a/tests/dummy/app/resources/post/edit/route.js +++ b/tests/dummy/app/resources/post/edit/route.js @@ -1,8 +1,8 @@ import Ember from 'ember'; -import Rollback from 'ember-easy-form-extensions/mixins/routes/rollback'; +import DirtyRecordHandler from 'ember-easy-form-extensions/mixins/routes/dirty-record-handler'; export default Ember.Route.extend( - Rollback, { + DirtyRecordHandler, { model: function() { return this.modelFor('post'); diff --git a/tests/dummy/app/resources/post/index/route.js b/tests/dummy/app/resources/post/index/route.js index b5e667e..fde77e4 100644 --- a/tests/dummy/app/resources/post/index/route.js +++ b/tests/dummy/app/resources/post/index/route.js @@ -3,7 +3,7 @@ import Ember from 'ember'; export default Ember.Route.extend({ model: function(params) { - return this.store.find('post', params.id); + return this.modelFor('post'); } }); diff --git a/tests/dummy/app/resources/post/model.js b/tests/dummy/app/resources/post/model.js index bb47fe7..549a0d5 100644 --- a/tests/dummy/app/resources/post/model.js +++ b/tests/dummy/app/resources/post/model.js @@ -4,20 +4,32 @@ import Ember from 'ember'; var attr = DS.attr; var PostModel = DS.Model.extend({ - title: attr('string'), - description: attr('string'), category: attr('string'), + description: attr('string'), + published: attr('boolean'), + title: attr('string'), views: attr('number'), - published: attr('boolean') + writtenOn: attr('date'), }); PostModel.reopenClass({ FIXTURES: [ { id: 1, - title: 'How to Ember', + category: 'ember', description: 'This is an introduction on how to Ember. Wow.', - category: 'ember' + published: true, + title: 'How to Ember', + views: 100, + writtenOn: moment().subtract(2, 'days') + }, { + id: 2, + category: 'rails', + description: 'This is an introduction on how to Rails. Wow.', + published: false, + title: 'How to Rails', + views: 0, + writtenOn: moment().subtract(7, 'days') } ] }); diff --git a/tests/dummy/app/resources/posts/new/route.js b/tests/dummy/app/resources/posts/new/route.js index 9f1f5da..66762d7 100644 --- a/tests/dummy/app/resources/posts/new/route.js +++ b/tests/dummy/app/resources/posts/new/route.js @@ -1,8 +1,8 @@ import Ember from 'ember'; -import DeleteRecord from 'ember-easy-form-extensions/mixins/routes/delete-record'; +import DirtyRecordHandler from 'ember-easy-form-extensions/mixins/routes/dirty-record-handler'; export default Ember.Route.extend( - DeleteRecord, { + DirtyRecordHandler, { model: function() { return this.store.createRecord('post'); diff --git a/tests/dummy/app/resources/posts/new/template.hbs b/tests/dummy/app/resources/posts/new/template.hbs index 920b83e..c3ead57 100644 --- a/tests/dummy/app/resources/posts/new/template.hbs +++ b/tests/dummy/app/resources/posts/new/template.hbs @@ -1,21 +1,36 @@ {{#form-wrapper}} {{#form-controls legend='Write a new post'}} - {{input-wrapper value=model.title placeholder='Title'}} - {{input-wrapper value=model.description type='textarea'}} + + {{input-wrapper + value=model.title + }} + + {{input-wrapper + value=model.description + type='textarea' + }} + {{input-wrapper value=model.category content=categories - type='select' }} + {{input-wrapper - type='number' value=model.views + type='number' }} + {{input-wrapper - type='checkbox' value=model.published + type='checkbox' + }} + + {{input-wrapper + value=model.writtenOn + type='date' }} + {{/form-controls}} {{form-submission}} @@ -24,13 +39,19 @@
Title
-
{{model.title}}
+
{{model.title}}
Description
-
{{model.description}}
+
{{model.description}}
+
Category
+
{{model.category}}
+
Views
+
{{model.views}}
+
Published
+
{{model.published}}
+
Written On
+
{{model.writtenOn}}
-{{errors.model.title.firstObject}} - {{#if editing}} {{destroy-submission}} {{/if}} From 26f49f1fb7a1c4d8562d048bcdeb9f8a1c3f0ddb Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Mon, 18 May 2015 23:33:20 -0400 Subject: [PATCH 17/73] Documentation added and input wrapper renamed --- README.md | 164 ++++++++++++++---- addon/components/form-wrapper.js | 1 - .../{input-wrapper.js => input-group.js} | 14 +- addon/initializers/easy-form-extensions.js | 32 ++++ addon/mixins/routes/dirty-record-handler.js | 2 + addon/services/easy-form.js | 2 +- .../{input-wrapper.hbs => input-group.hbs} | 0 app/acceptance-tests/form-layout.js | 1 + app/templates/form-inputs/checkbox.hbs | 5 +- app/templates/form-inputs/default.hbs | 27 +++ app/templates/form-inputs/select.hbs | 4 + app/templates/form-inputs/textarea.hbs | 16 +- tests/acceptance/form-layout-test.js | 23 +++ .../app/resources/posts/new/template.hbs | 12 +- 14 files changed, 256 insertions(+), 47 deletions(-) rename addon/components/{input-wrapper.js => input-group.js} (90%) rename addon/templates/components/{input-wrapper.hbs => input-group.hbs} (100%) create mode 100644 app/acceptance-tests/form-layout.js create mode 100644 tests/acceptance/form-layout-test.js diff --git a/README.md b/README.md index e7ab1ff..78a0080 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ This addon extends Ember EasyForm into the view and controller layers of your Em **This is also the easiest known way to use Easy Form with Ember 1.10 and HTMLBars.** +## Ember 1.11 Users + +Usage with Ember 1.11 and above is compatible with the 1.0.0.beta prerelease - see [here](https://github.com/sir-dunxalot/ember-easy-form-extensions/tree/ember-1.11). + +The `ember-1.11` branch is an evolved easy form API. It is very early stage and the API has detached form the original easy form syntax. You will need to update your templates accordingly. + ## Installation Uninstall any references to `ember-easy-form` and `ember-validations`. Then: @@ -23,8 +29,8 @@ The below code works out of the box but is also very customizable and extendible {{#form-wrapper}} {{#form-controls legend='Write a new post'}} - {{input title}} - {{input description as='text'}} + {{input value=model.title}} + {{input value=model.description type='textarea'}} {{/form-controls}} {{form-submission}} @@ -54,7 +60,7 @@ export default Ember.ObjectController.extend( // Validations run out of the box validations: { - title: { + 'model.title': { presence: true } } @@ -251,18 +257,23 @@ export default Ember.ObjectController.extend( The `saveButtonText` could then be used in your [`{{form-submission}}` component](#form-submission). -### Rollback (for routes) +### Dirty Record Handler (for routes) + +The diry record handler mixin is intended for use in routes. This mixin will check to see if the model is dirty and will do one of two things: -The rollback mixin is intended for use in routes where you are **editing** a model. This mixin will check to see if the model is dirty and will automatically rollback it's changes if it is. The most common reason for this to happen is the user navigates to the edit route of a resource and then clicks cancel. +- If there is a clean version of the model in the store it will rollback to it (i.e. if the user if editing a resource) +- If there is no clean version in the store it will delete the record (i.e. if the user is creating a new resource) + +The most common reason for this to happen is the user navigates to the edit or new route of a resource and then clicks cancel. ```js // app-name/routes/post/edit.js import Ember from 'ember'; -import Rollback from 'ember-easy-form-extensions/mixins/routes/rollback'; +import DirtyRecordHandler from 'ember-easy-form-extensions/mixins/routes/dirty-record-handler'; export default Ember.Route.extend( - Rollback, { + DirtyRecordHandler, { model: function(params) { return this.modelFor('post'); @@ -271,39 +282,34 @@ export default Ember.Route.extend( }); ``` -### Delete Record (for routes) +## Components -The delete record is intended for use in routes where you are creating a **new** record. This mixin will check to see if the model is dirty and will automatically rollback it's changes if it is. The most common reason for this to happen is the user navigates to the new route of a resource and then clicks cancel. +To extend the class of any components to overwrite or add functionality, just import them from this addon and then export them in your app. For example: ```js -// app-name/routes/posts/new.js - -import Ember from 'ember'; -import DeleteRecord from 'ember-easy-form-extensions/mixins/routes/delete-record'; - -export default Ember.Route.extend( - DeleteRecord, { +// app-name/components/form-submissison.js - model: function() { - return this.store.createRecord('post'); - } +import FormSubmissionComponent from 'ember-easy-form-extensions/components/form-submission'; +export default FormSubmissionComponent.extend({ + // Your functionality here }); ``` -## Components - -To extend the class of any components just import them from this addon and then export them in your app. For example: +To overwrite the default class names of any components, overwrite the options in the easy-form service: ```js -// app-name/components/form-submissison.js +// app/sevices/easy-form.js -import FormSubmissionComponent from 'ember-easy-form-extensions/components/form-submission'; +import EasyFormService from 'ember-easy-form-extensions/services/easy-form'; -export default FormSubmissionComponent.extend({ - // Your functionality here - className: ['buttons-group'] - classNames: ['form_submission'] +export default EasyFormService.extend({ + errorClass: 'error', + hintClass: 'hint', + inputClass: 'input', + inputGroupClass: 'input-wrapper', + formWrapperClass: 'form', + formControlsClass: 'controls', }); ``` @@ -321,24 +327,24 @@ The `{{#form-wrapper}}` component wraps your code in a `
` tag {{/form-wrapper}} ``` -You can use custom the base classname by passing a `className` attribute: +You can use HTML5 validations by setting the `novalidate` attribute to false: ```hbs {{!--app-name/templates/posts/new.hbs--}} -{{#form-wrapper className='form-static'}} +{{#form-wrapper novalidate=false}} {{!--Your inputs here--}} {{form-submission}} -{{/form-wrapper}} +{{/form-wrappexr}} ``` -Otherwise, this component work just like any other Ember component. - ### Form Controls The `{{#form-controls}}` component adds more sementicism to your templates. Use it **inside** your `{{#form-wrapper}}`: +This will render a `
` element, which groups related inputs together. + ```hbs {{!--app-name/templates/posts/new.hbs--}} @@ -351,10 +357,96 @@ The `{{#form-controls}}` component adds more sementicism to your templates. Use {{/form-wrapper}} ``` -Note two important things: -- `{{form-submission}}` goes **outside** the `{{#form-controls}}` -- `{{#form-controls}}` requires a `legend` attribute for accessibility +`{{#form-controls}}` expects a `legend` attribute for accessibility. + +You can also pass a `model` attribute to the controls. By default, this `{{form-controls}}` component will assume the model is at the path `model`. However, if this fieldset is for a nested model you might prefer passing that. It will resolve your input labels correctly: + +```hbs +{{!--app-name/templates/posts/new.hbs--}} + +{{#form-wrapper}} + {{#form-controls legend='Write a new post' model=model.post}} + {{!--Your inputs here--}} + {{/form-controls}} + + {{form-submission}} +{{/form-wrapper}} +``` + +### Input Group + +The heart of your form functionality is provided by the `{{input-group}}` component. This replaces Easy Form's overriding of Ember's `{{input}}` helper. + +Simply pass a value and optional type to render a label, input, and error (when present): + +```hbs +{{input-group value=model.title}} +{{!--Renders as...--}} +Title + +``` + +#### Textareas + +```hbs +{{input-group + value=model.description + type='textarea' +}} +``` + +#### Selects + +When you pass a `content` attribute the input will automatically use a ` type attribute */ this.render(); - assert.equal(component._state, 'inDOM', - 'The component should be inserted into the DOM after render'); + /* Check the type attributes we expect to detect based on the + property (e.g. password, email, etc) for HTML5 inputs */ + + const expectedTypeDetects = { + password: 'password', + email: 'email', + url: 'url', + color: 'color', + tel: 'tel', + phone: 'tel', + search: 'search' + }; + + for (const type in expectedTypeDetects) { + setOnComponent(component, 'property', type); + + assert.equal(component.get('type'), expectedTypeDetects[type], + 'Type should be autodetected by string matches when no type is set'); + + assert.equal(component.get('inputPartial'), 'form-inputs/default', + 'The form input partial should still be the default'); + + assert.equal(this.$(`#${inputId}`).attr('type'), type, + 'The type attribute should be bound to the input element'); + } + + /* Now check for selects */ + + setOnComponent(component, 'content', [1, 2, 3]); + + assert.equal(component.get('type'), 'select', + 'Type should be set to select when the content property is present (regardless of the property name)'); + + assert.equal(this.$(`#${inputId}`).attr('type'), type, + "The type attribute, 'select', should be bound to the input element"); + + /* Now, for fallbacks, check the type of the value */ + + const expectedValueDetects = { + number: 123, + date: new Date(), + checkbox: true, + }; + + for (const type in expectedValueDetects) { + const partialName = type === 'checkbox' ? type : 'default'; + + setOnComponent(component, 'value', expectedTypeDetects[type]); + + assert.equal(component.get('type'), type, + 'Type should be autodetected by matching the type of value when no type is set'); + + assert.equal(component.get('inputPartial'), `form-inputs/${partialName}`, + 'The form input partial should reflect the value detect'); + + assert.equal(this.$(`#${inputId}`).attr('type'), type, + 'The type attribute should be bound to the input element'); + } + + /* Finally, check setting the type manually using textarea */ + + // setOnComponent(component, 'type', 'textarea'); + +}); + +test('Value and property binding', function(assert) { + const property = 'fruit'; + const value = 'apples'; + + assert.expect(6); + + assert.notOk(component.get('bindingForValue'), + 'The component should not have created a binding for the value'); + + this.render(); + + setOnController(component, property, value); + + setOnComponent(component, 'property', property); + + assert.equal(component.get('label'), property, + 'The label should update once the property is bound'); + + initAttrs(component); + + assert.ok(!!component.get('bindingForValue'), + 'The component should have created a binding for the value'); + + assert.equal(component.get('value'), value, + 'The property value on the controller should be bound to the input group'); + + assert.equal(component.$().data('test'), `input-wrapper-for-${property}`, + 'The component should have the correct testig attribute bound'); + + destroy(component); + + assert.notOk(!!component.get('bindingForValue'), + 'The component should remove the binding for the value'); + +}); + +test('Action routing', function(assert) { + + assert.expect(0); + + // TODO + }); From 884fd76d64c50dac4c86a68c819f229db823ec4c Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sat, 11 Jul 2015 14:37:09 -0400 Subject: [PATCH 47/73] Error field refactored text property --- addon/components/error-field.js | 9 +++++++-- addon/components/input-group.js | 2 +- addon/templates/components/error-field.hbs | 2 +- tests/unit/components/error-field-test.js | 9 +++++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/addon/components/error-field.js b/addon/components/error-field.js index 8cbc30c..6802e3c 100644 --- a/addon/components/error-field.js +++ b/addon/components/error-field.js @@ -1,6 +1,5 @@ import Ember from 'ember'; import layout from '../templates/components/error-field'; -import toWords from '../utils/to-words'; const { computed, observer, on } = Ember; @@ -22,7 +21,13 @@ export default Ember.Component.extend({ tagName: 'span', validAction: 'setGroupAsValid', showError: false, - text: computed.oneWay('errors.firstObject'), + + text: computed('errors.firstObject', 'label', function() { + const error = this.get('errors.firstObject'); + const label = this.get('label'); + + return `${label} ${error}`; + }), formController: computed(function() { const hasFormController = this.nearestWithProperty('formController'); diff --git a/addon/components/input-group.js b/addon/components/input-group.js index ff2bf43..a4d43a8 100644 --- a/addon/components/input-group.js +++ b/addon/components/input-group.js @@ -99,7 +99,7 @@ export default Ember.Component.extend({ if (this.get('content')) { type = 'select'; - else if (property.match(/password/)) { + } else if (property.match(/password/)) { type = 'password'; } else if (property.match(/email/)) { type = 'email'; diff --git a/addon/templates/components/error-field.hbs b/addon/templates/components/error-field.hbs index 30af83a..f14cee9 100644 --- a/addon/templates/components/error-field.hbs +++ b/addon/templates/components/error-field.hbs @@ -1,5 +1,5 @@ {{#if errors.length}} {{#if showError}} - {{capitalize-string label}} {{text}} + {{capitalize-string text}} {{/if}} {{/if}} diff --git a/tests/unit/components/error-field-test.js b/tests/unit/components/error-field-test.js index 4ad4833..6427780 100644 --- a/tests/unit/components/error-field-test.js +++ b/tests/unit/components/error-field-test.js @@ -58,7 +58,9 @@ test('Properties', function(assert) { assert.ok(component.get('text').indexOf(property) > -1, 'The error text should contain the new property name'); - const element = this.$(); // Calls render + this.render(); + + const element = this.$(); assert.ok(element.hasClass(component.get('className')), 'The class name should be bound to the element'); @@ -110,7 +112,10 @@ test('The DOM', function(assert) { ]), }); - setPropertiesOnComponent(component, { property }); + setPropertiesOnComponent(component, { + property, + showError: true, + }); initAttrs(component); From f2be2d6e53ebfc70336db3c9fa42f571f7952991 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sat, 11 Jul 2015 16:48:44 -0400 Subject: [PATCH 48/73] Type property and partial test passing plus jshint fixes --- addon/components/input-group.js | 19 ++++-- .../components/form-submission-button-test.js | 2 +- tests/unit/components/form-submission-test.js | 1 + tests/unit/components/input-group-test.js | 65 ++++++++++++------- 4 files changed, 57 insertions(+), 30 deletions(-) diff --git a/addon/components/input-group.js b/addon/components/input-group.js index a4d43a8..a835b37 100644 --- a/addon/components/input-group.js +++ b/addon/components/input-group.js @@ -11,6 +11,7 @@ export default Ember.Component.extend({ className: 'input-wrapper', hint: null, + pathToInputPartials: 'form-inputs', property: null, newlyValidDuration: 3000, @@ -71,14 +72,18 @@ export default Ember.Component.extend({ }), inputPartial: computed('type', function() { - const container = this.get('container'); - const dir = 'form-inputs/'; - const type = this.get('type'); + const { container, pathToInputPartials, type } = this.getProperties( + [ 'container', 'pathToInputPartials', 'type' ] + ); - if (!!container.lookup(`template:${dir}${type}`)) { - return `${dir}${type}`; + /* Remove leading and trailing slashes for consistency */ + + const dir = pathToInputPartials.replace(/^\/|\/$/g, ''); + + if (!!container.lookup(`template:${dir}/${type}`)) { + return `${dir}/${type}`; } else { - return `${dir}default`; + return `${dir}/default`; } }), @@ -92,7 +97,7 @@ export default Ember.Component.extend({ return toWords(property); }), - type: computed('content', 'propertyWithoutModel', function() { + type: computed('content', 'propertyWithoutModel', 'value', function() { const property = this.get('propertyWithoutModel'); let type; diff --git a/tests/unit/components/form-submission-button-test.js b/tests/unit/components/form-submission-button-test.js index fb63135..b81bcaa 100644 --- a/tests/unit/components/form-submission-button-test.js +++ b/tests/unit/components/form-submission-button-test.js @@ -82,7 +82,7 @@ test('The DOM', function(assert) { test('Actions', function(assert) { - expect(1); + assert.expect(1); setPropertiesOnComponent(component, { action: 'orderBigMacs', diff --git a/tests/unit/components/form-submission-test.js b/tests/unit/components/form-submission-test.js index 83f945d..a2007d7 100644 --- a/tests/unit/components/form-submission-test.js +++ b/tests/unit/components/form-submission-test.js @@ -1,3 +1,4 @@ +import Ember from 'ember'; import { moduleForComponent, test } from 'ember-qunit'; import { renderingTests, diff --git a/tests/unit/components/input-group-test.js b/tests/unit/components/input-group-test.js index d7bde6b..47f01f6 100644 --- a/tests/unit/components/input-group-test.js +++ b/tests/unit/components/input-group-test.js @@ -23,7 +23,10 @@ moduleForComponent('input-group', 'Unit | Component | input group', { 'component:error-field', 'component:label-field', 'helper:capitalize-string', + 'template:form-inputs/checkbox', 'template:form-inputs/default', + 'template:form-inputs/select', + 'template:form-inputs/textarea', ], unit: true, @@ -130,9 +133,9 @@ test('Input attribute properties', function(assert) { }); test('Type property and partial', function(assert) { - const inputId = component .get('inputId'); + const inputId = component.get('inputId'); - // assert.expect(0); + assert.expect(24); /* Render early so we can check the type attribute */ @@ -151,41 +154,63 @@ test('Type property and partial', function(assert) { search: 'search' }; - for (const type in expectedTypeDetects) { + for (let type in expectedTypeDetects) { + const expectedType = expectedTypeDetects[type]; + setOnComponent(component, 'property', type); - assert.equal(component.get('type'), expectedTypeDetects[type], + assert.equal(component.get('type'), expectedType, 'Type should be autodetected by string matches when no type is set'); assert.equal(component.get('inputPartial'), 'form-inputs/default', 'The form input partial should still be the default'); - assert.equal(this.$(`#${inputId}`).attr('type'), type, + assert.equal(this.$(`#${inputId}`).attr('type'), expectedType, 'The type attribute should be bound to the input element'); } /* Now check for selects */ - setOnComponent(component, 'content', [1, 2, 3]); + setOnComponent(component, 'content', Em.A([1, 2, 3])); assert.equal(component.get('type'), 'select', 'Type should be set to select when the content property is present (regardless of the property name)'); - assert.equal(this.$(`#${inputId}`).attr('type'), type, + assert.equal(this.$(`#${inputId}`).prop('tagName').toLowerCase(), 'select', "The type attribute, 'select', should be bound to the input element"); - /* Now, for fallbacks, check the type of the value */ + /* Reset the component for the next set of tests */ - const expectedValueDetects = { - number: 123, - date: new Date(), - checkbox: true, - }; + /* Finally, check setting the type manually using textarea */ + + setOnComponent(component, 'type', 'textarea'); + + assert.equal(this.$(`#${inputId}`).prop('tagName').toLowerCase(), 'textarea', + "The type attribute, 'select', should be bound to the input element"); + +}); + +/* Now, for fallbacks, check the type of the value */ - for (const type in expectedValueDetects) { +const expectedValueDetects = { + number: 123, + date: new Date(), + checkbox: true, +}; + +for (let type in expectedValueDetects) { + test(`typeof value detection fallback for ${type} property`, function(assert) { + const inputId = component.get('inputId'); const partialName = type === 'checkbox' ? type : 'default'; + const value = expectedValueDetects[type]; + + assert.expect(3); + + setPropertiesOnComponent(component, { + value, + }); - setOnComponent(component, 'value', expectedTypeDetects[type]); + this.render(); assert.equal(component.get('type'), type, 'Type should be autodetected by matching the type of value when no type is set'); @@ -193,15 +218,11 @@ test('Type property and partial', function(assert) { assert.equal(component.get('inputPartial'), `form-inputs/${partialName}`, 'The form input partial should reflect the value detect'); - assert.equal(this.$(`#${inputId}`).attr('type'), type, + assert.equal(component.$(`#${inputId}`).attr('type'), type, 'The type attribute should be bound to the input element'); - } - /* Finally, check setting the type manually using textarea */ - - // setOnComponent(component, 'type', 'textarea'); - -}); + }); +} test('Value and property binding', function(assert) { const property = 'fruit'; From 51c58ed36ea9ff9fc187743e0df57fd02ccafac4 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sat, 11 Jul 2015 17:49:02 -0400 Subject: [PATCH 49/73] Validity tests for class name working --- addon/components/input-group.js | 4 +- tests/unit/components/input-group-test.js | 124 +++++++++++++--------- 2 files changed, 74 insertions(+), 54 deletions(-) diff --git a/addon/components/input-group.js b/addon/components/input-group.js index a835b37..5e899d3 100644 --- a/addon/components/input-group.js +++ b/addon/components/input-group.js @@ -179,7 +179,9 @@ export default Ember.Component.extend({ } run.later(this, function() { - this.set('isNewlyValid', false); + if (!this.get('isDestroying')) { + this.set('isNewlyValid', false); + } }, this.get('newlyValidDuration')); }), diff --git a/tests/unit/components/input-group-test.js b/tests/unit/components/input-group-test.js index 47f01f6..328ab85 100644 --- a/tests/unit/components/input-group-test.js +++ b/tests/unit/components/input-group-test.js @@ -81,48 +81,40 @@ test('Basic properties', function(assert) { }); -// TODO - Debug with wifi +test('Class name bindings', function(assert) { + const className = component.get('className'); + const done = assert.async(); // Enable asyns test + const newlyValidDuration = 10; -// test('Class name bindings', function(assert) { -// const className = component.get('className'); -// const newlyValidDuration = 100; + assert.expect(4); -// let shouldContinueTests = false; - -// assert.expect(4); - -// this.render(); - -// testClassNameBinding(assert, component); + this.render(); -// setPropertiesOnComponent(component, { newlyValidDuration }); + testClassNameBinding(assert, component); -// /* Set as invalid then look at class */ + setPropertiesOnComponent(component, { newlyValidDuration }); -// sendAction(component, 'setGroupAsInvalid'); -// assertClass(assert, component, `${className}-error`); + /* Set as invalid then look at class */ -// /* Set as invalid then look at class */ + sendAction(component, 'setGroupAsInvalid'); + assertClass(assert, component, `${className}-error`); -// sendAction(component, 'setGroupAsValid'); -// assertClass(assert, component, `${className}-newly-valid`); + /* Set as invalid then look at class */ -// /* Look at class after validity period is over */ + sendAction(component, 'setGroupAsValid'); + assertClass(assert, component, `${className}-newly-valid`); -// run.later(component, function() { + /* Look at class after validity period is over */ -// sendAction(component, 'setGroupAsValid'); -// assertClass(assert, component, `${className}-valid`); + run.later(component, function() { -// shouldContinueTests = true; -// }, newlyValidDuration); + sendAction(component, 'setGroupAsValid'); + assertClass(assert, component, `${className}-valid`); -// // Ember.Test.registerWaiter(this, function() { -// // console.log('checking', shouldContinueTests); -// // return false; -// // }); + done(); + }, newlyValidDuration); -// }); +}); test('Input attribute properties', function(assert) { @@ -135,7 +127,7 @@ test('Input attribute properties', function(assert) { test('Type property and partial', function(assert) { const inputId = component.get('inputId'); - assert.expect(24); + assert.expect(33); /* Render early so we can check the type attribute */ @@ -181,30 +173,26 @@ test('Type property and partial', function(assert) { /* Reset the component for the next set of tests */ - /* Finally, check setting the type manually using textarea */ - - setOnComponent(component, 'type', 'textarea'); - - assert.equal(this.$(`#${inputId}`).prop('tagName').toLowerCase(), 'textarea', - "The type attribute, 'select', should be bound to the input element"); - -}); + setPropertiesOnComponent(component, { + content: null, + property: 'bananas', + }); -/* Now, for fallbacks, check the type of the value */ + /* Now, for fallbacks, check the type of the value */ -const expectedValueDetects = { - number: 123, - date: new Date(), - checkbox: true, -}; + const expectedValueDetects = { + number: 123, + date: new Date(), + checkbox: true, + }; -for (let type in expectedValueDetects) { - test(`typeof value detection fallback for ${type} property`, function(assert) { - const inputId = component.get('inputId'); + for (let type in expectedValueDetects) { + // test(`typeof value detection fallback for ${type} property`, function(assert) { + // const inputId = component.get('inputId'); const partialName = type === 'checkbox' ? type : 'default'; const value = expectedValueDetects[type]; - assert.expect(3); + // assert.expect(3); setPropertiesOnComponent(component, { value, @@ -221,8 +209,17 @@ for (let type in expectedValueDetects) { assert.equal(component.$(`#${inputId}`).attr('type'), type, 'The type attribute should be bound to the input element'); - }); -} + // }); + } + + /* Finally, check setting the type manually using textarea */ + + setOnComponent(component, 'type', 'textarea'); + + assert.equal(this.$(`#${inputId}`).prop('tagName').toLowerCase(), 'textarea', + "The type attribute, 'select', should be bound to the input element and setabble manually"); + +}); test('Value and property binding', function(assert) { const property = 'fruit'; @@ -260,10 +257,31 @@ test('Value and property binding', function(assert) { }); -test('Action routing', function(assert) { +test('Validity action routing', function(assert) { - assert.expect(0); + assert.expect(3); - // TODO + /* Ensure properties begin as we want */ + + setPropertiesOnComponent(component, { + isValid: false, + newlyValidDuration: 0, + showError: false, + }); + + sendAction(component, 'showError'); + + assert.ok(component.get('showError'), + 'Calling the showError() action should set showError to true'); + + sendAction(component, 'setGroupAsValid'); + + assert.ok(component.get('isValid'), + 'Calling the setGroupAsValid() action should set isValid to true'); + + sendAction(component, 'setGroupAsInvalid'); + + assert.notOk(component.get('isValid'), + 'Calling the setGroupAsInvalid() action should set isValid to false'); }); From ce684b0dd51dda7cc45cdb7ec49d5155a2cfa7a3 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sat, 11 Jul 2015 17:57:15 -0400 Subject: [PATCH 50/73] test for input attribute bindings --- tests/unit/components/input-group-test.js | 25 ++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/unit/components/input-group-test.js b/tests/unit/components/input-group-test.js index 328ab85..6591eb1 100644 --- a/tests/unit/components/input-group-test.js +++ b/tests/unit/components/input-group-test.js @@ -118,9 +118,28 @@ test('Class name bindings', function(assert) { test('Input attribute properties', function(assert) { - assert.expect(0); + const properties = [ + 'collection', + 'content', + 'optionValuePath', + 'optionLabelPath', + 'selection', + 'multiple', + 'name', + 'placeholder', + 'prompt', + 'disabled', + ]; + + assert.expect(properties.length); + + properties.forEach(function(property) { + const value = component.get(property); + + assert.ok(value !== undefined, + `The ipnut group should have a binding for the input attribute ${property}`); - // TODO + }); }); @@ -163,7 +182,7 @@ test('Type property and partial', function(assert) { /* Now check for selects */ - setOnComponent(component, 'content', Em.A([1, 2, 3])); + setOnComponent(component, 'content', Ember.A([1, 2, 3])); assert.equal(component.get('type'), 'select', 'Type should be set to select when the content property is present (regardless of the property name)'); From a2e017215e93c9a0371ce8e7bab65731caac24a9 Mon Sep 17 00:00:00 2001 From: Duncan Graham Walker Date: Sat, 11 Jul 2015 18:05:01 -0400 Subject: [PATCH 51/73] Label field unit test --- addon/components/label-field.js | 2 +- addon/templates/components/input-group.hbs | 2 +- addon/templates/components/label-field.hbs | 2 +- tests/unit/components/label-field-test.js | 46 +++++++++++++++++++--- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/addon/components/label-field.js b/addon/components/label-field.js index 521defd..05dcbc4 100644 --- a/addon/components/label-field.js +++ b/addon/components/label-field.js @@ -6,7 +6,7 @@ export default Ember.Component.extend({ className: 'label', classNameBindings: ['className'], for: null, - label: null, + text: null, layout: layout, tagName: 'label', }); diff --git a/addon/templates/components/input-group.hbs b/addon/templates/components/input-group.hbs index c13fd56..2ea2b28 100644 --- a/addon/templates/components/input-group.hbs +++ b/addon/templates/components/input-group.hbs @@ -1,7 +1,7 @@ {{#if label}} {{label-field for=inputId - label=label + text=label }} {{!-- TODO - change label to text? --}} {{/if}} diff --git a/addon/templates/components/label-field.hbs b/addon/templates/components/label-field.hbs index fc36da3..bea1f81 100644 --- a/addon/templates/components/label-field.hbs +++ b/addon/templates/components/label-field.hbs @@ -1 +1 @@ -{{capitalize-string label}} +{{capitalize-string text}} diff --git a/tests/unit/components/label-field-test.js b/tests/unit/components/label-field-test.js index 9137144..014e8c8 100644 --- a/tests/unit/components/label-field-test.js +++ b/tests/unit/components/label-field-test.js @@ -1,4 +1,16 @@ import { moduleForComponent, test } from 'ember-qunit'; +import { + assertClass, + destroy, + initAttrs, + renderingTests, + sendAction, + setOnController, + setOnComponent, + setPropertiesOnComponent, + setupComponent, + testClassNameBinding +} from '../../helpers/unit/component'; let component; @@ -11,15 +23,37 @@ moduleForComponent('label-field', 'Unit | Component | label field', { }, }); -test('it renders', function(assert) { +test('Rendering', function(assert) { - assert.equal(component._state, 'preRender', - 'The component instance should be created'); + assert.expect(2); + + renderingTests(assert, this, component); +}); + +test('Properties', function(assert) { + const forId = '#ember-fake-123'; + const text = 'Group of bananas?'; + + assert.expect(4); this.render(); - assert.equal(component._state, 'inDOM'); + testClassNameBinding(assert, component); + + setPropertiesOnComponent(component, { + for: forId, + text + }); + + const $component = component.$(); + + assert.equal($component.html().trim(), text, + 'The label text property should appear in the template'); + + assert.equal($component.attr('for'), forId, + 'The for attribute should be bound to the element'); + + assert.equal($component.prop('tagName').toLowerCase(), 'label', + 'The element should be a