diff --git a/addon/validations/factory.js b/addon/validations/factory.js index f5f5b464..7f9f21c8 100644 --- a/addon/validations/factory.js +++ b/addon/validations/factory.js @@ -384,6 +384,7 @@ function createCPValidationFor(attribute, validations, owner) { */ function createTopLevelPropsMixin(validatableAttrs) { return Ember.Mixin.create({ + isWarning: and(...validatableAttrs.map(attr => `attrs.${attr}.isWarning`)).readOnly(), isValid: and(...validatableAttrs.map(attr => `attrs.${attr}.isValid`)).readOnly(), isValidating: or(...validatableAttrs.map(attr => `attrs.${attr}.isValidating`)).readOnly(), isDirty: or(...validatableAttrs.map(attr => `attrs.${attr}.isDirty`)).readOnly(), diff --git a/addon/validations/result-collection.js b/addon/validations/result-collection.js index 732699cb..4c090128 100644 --- a/addon/validations/result-collection.js +++ b/addon/validations/result-collection.js @@ -54,6 +54,22 @@ export default Ember.Object.extend({ set(this, 'content', emberArray(get(this, 'content'))); }, + /** + * ```javascript + * // Examples + * get(user, 'validations.isWarning') + * get(user, 'validations.attrs.username.isWarning') + * ``` + * + * @property isWarning + * @default false + * @readOnly + * @type {Boolean} + */ + isWarning: computed('content.@each.isWarning', cycleBreaker(function () { + return get(this, 'content').isEvery('isWarning', true); + }, false)).readOnly(), + /** * ```javascript * // Examples diff --git a/bower.json b/bower.json index 65a70986..120c4ad0 100644 --- a/bower.json +++ b/bower.json @@ -5,7 +5,8 @@ "ember-cli-shims": "0.1.1", "ember-cli-test-loader": "0.2.2", "moment": ">= 2.8.0", - "moment-timezone": ">= 0.1.0" + "moment-timezone": ">= 0.1.0", + "font-awesome": "~4.5.0" }, "devDependencies": { "blanket": "5e94fc30f2e694bb5c3718ddcbf60d467f4b4d26", diff --git a/ember-cli-build.js b/ember-cli-build.js index 4ac39137..185ff557 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -4,7 +4,8 @@ var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function(defaults) { var app = new EmberAddon(defaults, { - // Add options here + snippetSearchPaths: ['addon','tests/dummy/app'], + snippetPaths: ['snippets','tests/dummy/snippets'] }); /* diff --git a/package.json b/package.json index 8708cabd..ceb811ba 100644 --- a/package.json +++ b/package.json @@ -41,17 +41,20 @@ "ember-cli-app-version": "^1.0.0", "ember-cli-blanket": "0.9.5", "ember-cli-dependency-checker": "^1.2.0", + "ember-cli-font-awesome": "1.5.2", "ember-cli-github-pages": "^0.1.0", "ember-cli-htmlbars": "^1.0.3", "ember-cli-htmlbars-inline-precompile": "^0.3.1", "ember-cli-inject-live-reload": "^1.4.0", "ember-cli-jshint": "^1.0.0", + "ember-cli-less": "1.5.3", "ember-cli-moment-shim": "2.0.0", "ember-cli-qunit": "^2.0.2", "ember-cli-release": "^1.0.0-beta.1", "ember-cli-sri": "^2.1.0", "ember-cli-uglify": "^1.2.0", "ember-cli-yuidoc": "0.8.4", + "ember-code-snippet": "1.3.0", "ember-data": "^2.6.0", "ember-disable-prototype-extensions": "^1.1.0", "ember-disable-proxy-controllers": "^1.0.1", @@ -59,7 +62,7 @@ "ember-load-initializers": "^0.5.1", "ember-moment": "6.1.0", "ember-resolver": "^2.0.3", - "ember-try": "0.2.4", + "ember-truth-helpers": "1.2.0", "loader.js": "^4.0.1", "moment": "2.13.0", "moment-timezone": "0.5.4", diff --git a/tests/acceptance/dummy-index-test.js b/tests/acceptance/dummy-index-test.js index 021f887c..aa40a234 100644 --- a/tests/acceptance/dummy-index-test.js +++ b/tests/acceptance/dummy-index-test.js @@ -29,8 +29,8 @@ test("Page Loads", function(assert) { assert.expect(2); visit('/'); andThen(function() { - assert.equal(find('.demo .info h1').text(), "CP Validations"); - assert.equal(find('.demo .form .register h2').text(), "Create an Account"); + assert.equal(find('a.navbar-brand').text().trim(), "CP Validations"); + assert.equal(find('.form .register h2').text(), "Create an Account"); }); }); @@ -43,11 +43,22 @@ test("Helper tooltips", function(assert) { }); }); +test("Invalid form submit", function(assert) { + visit('/'); + andThen(function() { + click('#signup'); + }); + + andThen(function() { + assert.equal(find('.form .alert').text().trim(), 'Please fix all the errors below before continuing.'); + }); +}); + test("Valid form submit", function(assert) { visit('/'); andThen(function() { Object.keys(validInputValues).forEach((input) => { - let $input = find(`.demo .validated-input input[name="${input}"]`); + let $input = find(`.validated-input input[name="${input}"]`); assert.ok($input, `${input} found`); fillIn($input, validInputValues[input]); assert.ok($input.parent('.validated-input.has-success'), `${input} success`); @@ -66,7 +77,7 @@ test("Invalid to valid email", function(assert) { visit('/'); var $input; andThen(function() { - $input = find('.demo .validated-input input[name="email"]'); + $input = find('.validated-input input[name="email"]'); assert.ok($input); fillIn($input, 'invalid-email'); }); @@ -79,4 +90,3 @@ test("Invalid to valid email", function(assert) { assert.ok($input.parent('.validated-input.has-success')); }); }); - diff --git a/tests/dummy/app/components/validated-input.js b/tests/dummy/app/components/validated-input.js index 7f9b0e96..c66059bb 100644 --- a/tests/dummy/app/components/validated-input.js +++ b/tests/dummy/app/components/validated-input.js @@ -3,6 +3,7 @@ * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ +// BEGIN-SNIPPET validated-input import Ember from 'ember'; const { @@ -43,3 +44,4 @@ export default Ember.Component.extend({ return (this.get('validation.isDirty') || this.get('didValidate')) && this.get('isValid') && !isEmpty(this.get('validation.warnings')); }) }); +// END-SNIPPET diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index aa36f6f5..0004479f 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -13,31 +13,41 @@ export default Ember.Controller.extend({ actions: { showCode() { - this.toggleProperty('showCode'); - }, + this.toggleProperty('showCode'); + }, - submit() { - var model = this.get('model'); - model.validate().then(({ - model, validations - }) => { - if (validations.get('isValid')) { - this.setProperties({ - showAlert: false, - isRegistered: true, - showCode: false - }); - } else { - this.set('showAlert', true); - } - this.set('didValidate', true); - }, (errors) => { + submit() { + var model = this.get('model'); + model.validate().then(({ + model, + validations + }) => { + if (validations.get('isValid')) { + this.setProperties({ + showAlert: false, + isRegistered: true, + showCode: false + }); + } else { + this.set('showAlert', true); + } + this.set('didValidate', true); + }, (errors) => { - }); - }, + }); + }, - dismissAlert() { - this.set('showAlert', false); - } + dismissAlert() { + this.set('showAlert', false); + }, + + reset() { + this.setProperties({ + showAlert: false, + isRegistered: false, + showCode: false, + didValidate: false + }); + } } }); diff --git a/tests/dummy/app/index.html b/tests/dummy/app/index.html index cc86fad1..e14ae6ba 100644 --- a/tests/dummy/app/index.html +++ b/tests/dummy/app/index.html @@ -3,16 +3,14 @@ - Ember CP Validations Demo + Ember CP Validations {{content-for 'head'}} - - - + @@ -22,8 +20,7 @@ {{content-for 'body'}} - - + {{content-for 'body-footer'}} diff --git a/tests/dummy/app/models/user-detail.js b/tests/dummy/app/models/user-detail.js index 194f10a2..3432caed 100644 --- a/tests/dummy/app/models/user-detail.js +++ b/tests/dummy/app/models/user-detail.js @@ -3,19 +3,14 @@ * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ + // BEGIN-SNIPPET user-detail-model import Ember from 'ember'; import DS from 'ember-data'; import moment from 'moment'; -import { - validator, buildValidations -} -from 'ember-cp-validations'; +import { validator, buildValidations } from 'ember-cp-validations'; -const { - computed -} = Ember; - -var attr = DS.attr; +const { computed } = Ember; +const { attr } = DS; var Validations = buildValidations({ firstName: validator('presence', true), @@ -64,3 +59,4 @@ export default DS.Model.extend(Validations, { "phone": attr('string'), "url": attr('string') }); +// END-SNIPPET diff --git a/tests/dummy/app/models/user.js b/tests/dummy/app/models/user.js index 7b88280f..2719c47d 100644 --- a/tests/dummy/app/models/user.js +++ b/tests/dummy/app/models/user.js @@ -3,15 +3,13 @@ * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ + // BEGIN-SNIPPET user-model import Ember from 'ember'; import DS from 'ember-data'; import { validator, buildValidations } from 'ember-cp-validations'; -const { - computed -} = Ember; - -var attr = DS.attr; +const { computed } = Ember; +const { attr } = DS; var Validations = buildValidations({ username: { @@ -67,3 +65,4 @@ export default DS.Model.extend(Validations, { minLength: 5 }); +// END-SNIPPET diff --git a/tests/dummy/app/styles/app.less b/tests/dummy/app/styles/app.less new file mode 100644 index 00000000..8edc4f20 --- /dev/null +++ b/tests/dummy/app/styles/app.less @@ -0,0 +1,29 @@ +@accent-color: #f23818; + +@import "./navbar.less"; +@import "./form.less"; +@import "./code-snippet.less"; + +body, +html { + min-height: 100%; + min-width: 100%; + background-color: #F3F3F3; + height: 100%; + font-family: 'Open Sans', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + > div { + height: 100%; + display: flex; + flex-direction: column; + } +} + +.content { + display: flex; + justify-content: center; + padding: 25px; + flex: 1 0 auto; +} diff --git a/tests/dummy/app/styles/code-snippet.less b/tests/dummy/app/styles/code-snippet.less new file mode 100644 index 00000000..9d2d4303 --- /dev/null +++ b/tests/dummy/app/styles/code-snippet.less @@ -0,0 +1,51 @@ +@background-color: #fdfdfd; + +.snippet-container { + background: @background-color; + align-self: flex-start; + overflow: auto; + width: 0; + height: 670px; + margin: auto 0; + -webkit-transition: width 1s ease; + -moz-transition: width 1s ease; + -o-transition: width 1s ease; + transition: width 1s ease; + border-radius: 3px; + padding: 15px 10px; + margin-left: -20px; + z-index: 0; + + &.show { + width: 600px; + margin-left: -2px; + box-shadow: 3px 3px 10px 1px rgba(0, 0, 0, 0.15); + } + + .nav-tabs { + li > a { + color: #8A8A8A; + border-radius: 2px 2px 0 0; + font-size: 12px; + } + li.active > a { + background-color: @background-color; + color: #696969; + } + } + .tab-content { + background-color: @background-color; + max-height: 600px; + overflow: auto; + + > .active { + padding: 15px 15px 0; + } + } + pre { + border: none; + margin: 0; + padding: 0; + background-color: @background-color; + } +} diff --git a/tests/dummy/app/styles/app.css b/tests/dummy/app/styles/form.less similarity index 73% rename from tests/dummy/app/styles/app.css rename to tests/dummy/app/styles/form.less index 49a7086b..9789e1be 100644 --- a/tests/dummy/app/styles/app.css +++ b/tests/dummy/app/styles/form.less @@ -1,53 +1,18 @@ -/** - * Copyright 2016, Yahoo! Inc. - * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. - */ - -html { - width: 100%; - height: 100%; -} - -body { - margin: 0; - padding: 0; - font-family: 'Open Sans', sans-serif; - background: #e9e9e9; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.demo { - margin-bottom: 50px; +.form { text-align: center; -} - -.demo .info { - width: 500px; - margin: 25px auto; - text-align: center; -} - -.demo .info h1 { - margin: 0; - padding: 0; - font-size: 24px; - font-weight: 400; - color: #333333; -} - -.demo .form { position: relative; background: #ffffff; width: 485px; - margin: 25px auto 0; padding: 40px; - border-top: 5px solid #f23818; + border-top: 5px solid @accent-color; border-radius: 3px; - box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.15); + box-shadow: 5px 5px 10px 1px rgba(0, 0, 0, 0.15); + flex: 0; + z-index: 5; + align-self: center; } -.demo .form input { +.form input { outline: none; display: block; width: 100%; @@ -66,7 +31,7 @@ body { box-shadow: none; } -.demo .form .form-group { +.form .form-group { margin: 0 0 20px; } @@ -83,27 +48,34 @@ body { border-color: rgb(31, 187, 20); } -.demo .form input:focus { - outline: none; +.has-success .form-control { + border-color: #d9d9d9; +} + +input.form-control:focus { color: #333333; + border-color: #b9b9b9; + outline: none; + box-shadow: none; + -webkit-box-shadow: none; } -.demo .form h2 { +.form h2 { margin: -15px 0 25px 0; line-height: 1; - color: #f23818; + color: @accent-color; font-size: 18px; font-weight: 400; } -.demo .form form h4 { +.form form h4 { margin-bottom: 20px; color: rgb(107, 106, 106); font-weight: 400; font-size: 16px; } -.demo .form .alert { +.form .alert { position: relative; background: #f3f3f3; color: #666666; @@ -113,7 +85,7 @@ body { text-align: left; } -.demo .form .alert .icon-remove { +.form .alert .icon-remove { cursor: pointer; position: absolute; top: 50%; @@ -126,10 +98,10 @@ body { float: right; } -.demo .form button { +.form button { cursor: pointer; outline: none; - background: #f23818; + background: @accent-color; width: 100%; padding: 10px 15px; margin-bottom: 25px; @@ -144,7 +116,7 @@ body { transition: all 0.3s linear 0s; } -.demo .form .section { +.form .section { position: relative; } @@ -173,7 +145,7 @@ body { border-bottom: 10px solid transparent; } -.demo .form .section-info.left { +.form .section-info.left { left: -325px; } @@ -206,8 +178,8 @@ body { top: -50%; } -.demo .form footer { - background: #f2f2f2; +.form footer { + background: #eae9e9; width: 485px; padding: 15px 40px; margin: 0 0 -40px -40px; @@ -217,18 +189,18 @@ body { text-align: center; } -.demo .form footer a { +.form footer a { color: #333333; text-decoration: none; } -.demo .form h2.success { +.form h2.success { font-size: 40px; margin-bottom: 40px; padding-top: 20px; } -.demo .form .tomster { +.form .tomster { position: absolute; top: 0; z-index: -1; @@ -242,7 +214,7 @@ body { animation-fill-mode: forwards; } -.demo .form .registered .icon-success { +.form .registered .icon-success { font-size: 160px; color: #A5A5A5; margin-bottom: 15px; @@ -278,32 +250,10 @@ body { color: rgb(255, 165, 31); } -.demo a.show-code { +a.show-code { cursor: pointer; } -.code { - display: none; - position: relative; - width: 600px; - margin: -10px auto 25px auto; - background: rgba(0, 0, 0, 0.8); - padding: 0 25px; -} - -.code:before { - left: 295px; -} - -.code .prettyprint { - background-color: transparent; - border: none; -} - -.code.show { - display: block; -} - @-webkit-keyframes peek { from { top: 0; diff --git a/tests/dummy/app/styles/navbar.less b/tests/dummy/app/styles/navbar.less new file mode 100644 index 00000000..4bd6ce8f --- /dev/null +++ b/tests/dummy/app/styles/navbar.less @@ -0,0 +1,41 @@ +.navbar.navbar-default { + background-color: white; + margin: 0; + border-radius: 0; + border-width: 0; + border-bottom-width: 1px; + + .navbar-brand { + color: #797979; + font-size: 16px; + font-weight: 400; + img { + height: 32px; + display: inline-block; + margin-top: -15px; + } + span { + font-size: 12px; + vertical-align: super; + text-transform: uppercase; + color: #444444; + } + } + .navbar-nav > li > a { + font-size: 14px; + font-weight: 200; + &.github { + font-size: 24px; + } + } + .navbar-nav > li > a:focus, + .navbar-nav > li > a:hover { + color: @accent-color; + } + .navbar-nav > .active > a, + .navbar-nav > .active > a:focus, + .navbar-nav > .active > a:hover { + color: @accent-color; + background-color: transparent; + } +} diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index c24cd689..18eedccf 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -1 +1,32 @@ -{{outlet}} + + +
+ {{outlet}} +
diff --git a/tests/dummy/app/templates/components/validated-input.hbs b/tests/dummy/app/templates/components/validated-input.hbs index de32bc9e..8dbcfa96 100644 --- a/tests/dummy/app/templates/components/validated-input.hbs +++ b/tests/dummy/app/templates/components/validated-input.hbs @@ -1,3 +1,4 @@ +{{!-- BEGIN-SNIPPET validated-input --}}
{{input type=type value=value placeholder=placeholder class="form-control" name=valuePath}} {{#if isValid}} @@ -18,3 +19,4 @@ {{/if}}
+{{!-- END-SNIPPET --}} diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs index 433cbf7c..70a705ac 100644 --- a/tests/dummy/app/templates/index.hbs +++ b/tests/dummy/app/templates/index.hbs @@ -1,71 +1,77 @@ -Fork me on GitHub +
+ {{#unless isRegistered}} +
+

Create an Account

-
-
- Ember -

CP Validations

-
-
- {{#unless isRegistered}} -
-

Create an Account

- - {{#if showAlert}} -
-
- Please fix all the errors below before continuing. -
- {{/if}} -
-
- - {{validated-input model=model valuePath='username' placeholder='Username'}} - {{validated-input type='password' model=model valuePath='password' placeholder='Password'}} - {{validated-input model=model valuePath='email' placeholder='Email'}} - {{validated-input model=model valuePath='emailConfirmation' placeholder='Verify Email'}} + {{#if showAlert}} +
+
+ Please fix all the errors below before continuing. +
+ {{/if}} + +
+ {{#unless showCode}} + + {{/unless}} + + {{validated-input model=model valuePath='username' placeholder='Username'}} {{validated-input type='password' model=model valuePath='password' placeholder='Password'}} {{validated-input model=model valuePath='email' placeholder='Email'}} {{validated-input + model=model valuePath='emailConfirmation' placeholder='Verify Email'}} +
-

About Me

+

About Me

-
- - {{validated-input model=model.details valuePath='firstName' placeholder='First Name'}} - {{validated-input model=model.details valuePath='lastName' placeholder='Last Name'}} - {{validated-input model=model.details valuePath='dob' placeholder='Date of Birth'}} - {{validated-input model=model.details valuePath='phone' placeholder='Phone #'}} - {{validated-input model=model.details valuePath='url' placeholder='URL'}} +
+ {{#unless showCode}} + -
- - + {{/unless}} + + {{validated-input model=model.details valuePath='firstName' placeholder='First Name'}} {{validated-input model=model.details valuePath='lastName' placeholder='Last Name'}} {{validated-input model=model.details valuePath='dob' placeholder='Date of Birth'}} + {{validated-input model=model.details valuePath='phone' placeholder='Phone #'}} {{validated-input model=model.details valuePath='url' placeholder='URL'}} +
+
+ {{#unless showCode}} + - -
- {{else}} -
- -
-

Success

+ {{/unless}} +
- {{/unless}} - +
+ {{else}} +
+ {{!-- --}} +
+

Success

+
+ {{/unless}} +
- \ No newline at end of file +{{#unless isRegistered}} +
+ +
+
{{code-snippet name="user-model.js"}}
+
{{code-snippet name="user-detail-model.js"}}
+
{{code-snippet name="validated-input.js"}}
+
{{code-snippet name="validated-input.hbs"}}
+
+
+{{/unless}}