Skip to content

Commit

Permalink
Form field icons load in default 'grey' state
Browse files Browse the repository at this point in the history
ref TryGhost#5652

- validations can be in default, success or error state
- adds check for 'hasValidated' if the validations haven't fired yet, the field is in the default state
- hasValidated is an Ember.Array which tracks the state for each field
  • Loading branch information
ErisDS committed Sep 1, 2015
1 parent 40f82b6 commit 9b6214b
Show file tree
Hide file tree
Showing 17 changed files with 71 additions and 39 deletions.
11 changes: 9 additions & 2 deletions core/client/app/components/gh-form-group.js
Expand Up @@ -13,10 +13,17 @@ export default Ember.Component.extend({

errors: null,
property: '',
hasValidated: Ember.A(),

errorClass: Ember.computed('errors.[]', 'property', function () {
errorClass: Ember.computed('errors.[]', 'property', 'hasValidated.[]', function () {
var property = this.get('property'),
errors = this.get('errors');
errors = this.get('errors'),
hasValidated = this.get('hasValidated');

// If we haven't yet validated this field, there is no validation class needed
if (!hasValidated || !hasValidated.contains(property)) {
return '';
}

if (errors) {
return errors.get(property) ? 'error' : 'success';
Expand Down
1 change: 1 addition & 0 deletions core/client/app/controllers/reset.js
Expand Up @@ -34,6 +34,7 @@ export default Ember.Controller.extend(ValidationEngine, {
var credentials = this.getProperties('newPassword', 'ne2Password', 'token'),
self = this;
this.set('flowErrors', '');
this.get('hasValidated').addObjects((['newPassword', 'ne2Password']));
this.validate().then(function () {
self.toggleProperty('submitting');
ajax({
Expand Down
4 changes: 2 additions & 2 deletions core/client/app/controllers/settings/general.js
Expand Up @@ -79,8 +79,8 @@ export default Ember.Controller.extend(SettingsSaveMixin, {
},

actions: {
validate: function () {
this.get('model').validate(arguments);
validate: function (property) {
this.get('model').validate({property: property});
},

checkPostsPerPage: function () {
Expand Down
1 change: 1 addition & 0 deletions core/client/app/controllers/settings/tags.js
Expand Up @@ -53,6 +53,7 @@ export default Ember.Controller.extend(PaginationMixin, SettingsMenuMixin, {
}

activeTag.set(propKey, newValue);
activeTag.get('hasValidated').addObject(propKey);

activeTag.save().catch(function (error) {
if (error) {
Expand Down
9 changes: 7 additions & 2 deletions core/client/app/controllers/setup/three.js
Expand Up @@ -6,6 +6,7 @@ export default Ember.Controller.extend({
two: Ember.inject.controller('setup/two'),

errors: DS.Errors.create(),
hasValidated: Ember.A(),
users: '',
ownerEmail: Ember.computed.alias('two.email'),
submitting: false,
Expand Down Expand Up @@ -63,17 +64,21 @@ export default Ember.Controller.extend({

validate: function () {
var errors = this.get('errors'),
validationResult = this.get('validationResult');
validationResult = this.get('validationResult'),
property = 'users';

errors.clear();

// If property isn't in the `hasValidated` array, add it to mark that this field can show a validation result
this.get('hasValidated').addObject(property);

if (validationResult === true) { return true; }

validationResult.forEach(function (error) {
// Only one error type here so far, but one day the errors might be more detailed
switch (error.error) {
case 'email':
errors.add('users', error.user + ' is not a valid email.');
errors.add(property, error.user + ' is not a valid email.');
}
});

Expand Down
8 changes: 5 additions & 3 deletions core/client/app/controllers/setup/two.js
Expand Up @@ -50,25 +50,27 @@ export default Ember.Controller.extend(ValidationEngine, {

actions: {
preValidate: function (model) {
var self = this;
// Only triggers validation if a value has been entered, preventing empty errors on focusOut
if (this.get(model)) {
if (model === 'email') {
self.send('handleEmail');
this.send('handleEmail');
}
this.validate({property: model});
}
},

setup: function () {
var self = this,
data = self.getProperties('blogTitle', 'name', 'email', 'password', 'image'),
setupProperties = ['blogTitle', 'name', 'email', 'password', 'image'],
data = self.getProperties(setupProperties),
notifications = this.get('notifications'),
config = this.get('config'),
method = this.get('blogCreated') ? 'PUT' : 'POST';

this.toggleProperty('submitting');
this.set('flowErrors', '');

this.get('hasValidated').addObjects(setupProperties);
this.validate().then(function () {
ajax({
url: self.get('ghostPaths.url').api('authentication', 'setup'),
Expand Down
11 changes: 9 additions & 2 deletions core/client/app/controllers/signin.js
Expand Up @@ -5,6 +5,7 @@ import {request as ajax} from 'ic-ajax';
export default Ember.Controller.extend(ValidationEngine, {
submitting: false,
loggingIn: false,
authProperties: ['identification', 'password'],

ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
Expand All @@ -18,7 +19,7 @@ export default Ember.Controller.extend(ValidationEngine, {
var self = this,
model = this.get('model'),
authStrategy = 'ghost-authenticator:oauth2-password-grant',
data = model.getProperties('identification', 'password');
data = model.getProperties(this.authProperties);

this.get('session').authenticate(authStrategy, data).then(function () {
self.toggleProperty('loggingIn');
Expand All @@ -28,7 +29,9 @@ export default Ember.Controller.extend(ValidationEngine, {
if (err.errors) {
self.set('flowErrors', err.errors[0].message.string);

if (err.errors[0].message.string.match(/no user with that email/)) {
// this catches both 'no user' and 'user inactive' errors
// long term, we probably need to introduce error codes from the server
if (err.errors[0].message.string.match(/user with that email/)) {
self.get('model.errors').add('identification', '');
}

Expand All @@ -49,6 +52,8 @@ export default Ember.Controller.extend(ValidationEngine, {
// browsers and password managers that don't send proper events on autofill
$('#login').find('input').trigger('change');

// This is a bit dirty, but there's no other way to ensure the properties are set as well as 'signin'
this.get('hasValidated').addObjects(this.authProperties);
this.validate({property: 'signin'}).then(function () {
self.toggleProperty('loggingIn');
self.send('authenticate');
Expand All @@ -67,6 +72,8 @@ export default Ember.Controller.extend(ValidationEngine, {
self = this;

this.set('flowErrors', '');
// This is a bit dirty, but there's no other way to ensure the properties are set as well as 'forgotPassword'
this.get('hasValidated').addObject('identification');
this.validate({property: 'forgotPassword'}).then(function () {
self.toggleProperty('submitting');

Expand Down
4 changes: 3 additions & 1 deletion core/client/app/controllers/signup.js
Expand Up @@ -42,13 +42,15 @@ export default Ember.Controller.extend(ValidationEngine, {
signup: function () {
var self = this,
model = this.get('model'),
data = model.getProperties('name', 'email', 'password', 'token'),
setupProperties = ['name', 'email', 'password', 'token'],
data = model.getProperties(setupProperties),
image = this.get('image'),

notifications = this.get('notifications');

this.set('flowErrors', '');

this.get('hasValidated').addObjects(setupProperties);
this.validate().then(function () {
self.toggleProperty('submitting');
ajax({
Expand Down
10 changes: 9 additions & 1 deletion core/client/app/mixins/validation-engine.js
Expand Up @@ -42,6 +42,10 @@ export default Ember.Mixin.create({
// ember-data models because they essentially use the same thing
errors: DS.Errors.create(),

// Store whether a property has been validated yet, so that we know whether or not
// to show error / success validation for a field
hasValidated: Ember.A(),

/**
* Passes the model to the validator specified by validationType.
* Returns a promise that will resolve if validation succeeds, and reject if not.
Expand All @@ -60,7 +64,8 @@ export default Ember.Mixin.create({

var model = this,
type,
validator;
validator,
hasValidated;

if (opts.model) {
model = opts.model;
Expand All @@ -72,6 +77,7 @@ export default Ember.Mixin.create({

type = this.get('validationType') || model.get('validationType');
validator = this.get('validators.' + type) || model.get('validators.' + type);
hasValidated = this.get('hasValidated');

opts.validationType = type;

Expand All @@ -83,6 +89,8 @@ export default Ember.Mixin.create({
}

if (opts.property) {
// If property isn't in `hasValidated`, add it to mark that this field can show a validation result
hasValidated.addObject(opts.property);
model.get('errors').remove(opts.property);
} else {
model.get('errors').clear();
Expand Down
4 changes: 2 additions & 2 deletions core/client/app/templates/reset.hbs
Expand Up @@ -2,10 +2,10 @@
<div class="gh-flow-content-wrap">
<section class="gh-flow-content fade-in">
<form id="reset" class="gh-signin" method="post" novalidate="novalidate" {{action "submit" on="submit"}}>
{{#gh-form-group errors=errors property="newPassword"}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="newPassword"}}
{{gh-input type="password" name="newpassword" placeholder="Password" class="password" autocorrect="off" autofocus="autofocus" value=newPassword}}
{{/gh-form-group}}
{{#gh-form-group errors=errors property="ne2Password"}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="ne2Password"}}
{{gh-input type="password" name="ne2password" placeholder="Confirm Password" class="password" autocorrect="off" autofocus="autofocus" value=ne2Password}}
{{/gh-form-group}}

Expand Down
6 changes: 3 additions & 3 deletions core/client/app/templates/settings/general.hbs
Expand Up @@ -10,14 +10,14 @@
<form id="settings-general" novalidate="novalidate">
<fieldset>

{{#gh-form-group errors=model.errors property="title"}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="title"}}
<label for="blog-title">Blog Title</label>
{{gh-input id="blog-title" class="gh-input" name="general[title]" type="text" value=model.title focusOut=(action "validate" "title")}}
{{gh-error-message errors=model.errors property="title"}}
<p>The name of your blog</p>
{{/gh-form-group}}

{{#gh-form-group class="description-container" errors=model.errors property="description"}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="description" class="description-container"}}
<label for="blog-description">Blog Description</label>
{{gh-textarea id="blog-description" class="gh-input" name="general[description]" value=model.description focusOut=(action "validate" "description")}}
{{gh-error-message errors=model.errors property="description"}}
Expand Down Expand Up @@ -93,7 +93,7 @@
</div>

{{#if model.isPrivate}}
{{#gh-form-group errors=model.errors property="password"}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="password"}}
{{gh-input name="general[password]" type="text" value=model.password focusOut=(action "validate" "password")}}
{{gh-error-message errors=model.errors property="password"}}
<p>This password will be needed to access your blog. All search engine optimization and social features are now disabled. This password is stored in plaintext.</p>
Expand Down
6 changes: 3 additions & 3 deletions core/client/app/templates/settings/tags/settings-menu.hbs
Expand Up @@ -10,7 +10,7 @@
<div class="settings-menu-content">
{{gh-uploader uploaded="setCoverImage" canceled="clearCoverImage" description="Add tag image" image=activeTag.image initUploader="setUploaderReference" tagName="section"}}
<form>
{{#gh-form-group errors=activeTag.errors property="name"}}
{{#gh-form-group errors=activeTag.errors hasValidated=activeTag.hasValidated property="name"}}
<label for="tag-name">Name</label>
{{gh-input id="tag-name" name="name" type="text" value=activeTagNameScratch focus-out="saveActiveTagName"}}
{{gh-error-message errors=activeTag.errors property="name"}}
Expand Down Expand Up @@ -55,14 +55,14 @@

<div class="settings-menu-content">
<form>
{{#gh-form-group errors=activeTag.errors property="meta_title"}}
{{#gh-form-group errors=activeTag.errors hasValidated=activeTag.hasValidated property="meta_title"}}
<label for="meta-title">Meta Title</label>
{{gh-input id="meta-title" name="meta_title" type="text" value=activeTagMetaTitleScratch focus-out="saveActiveTagMetaTitle"}}
{{gh-error-message errors=activeTag.errors property="meta_title"}}
<p>Recommended: <b>70</b> characters. You’ve used {{gh-count-down-characters activeTagMetaTitleScratch 70}}</p>
{{/gh-form-group}}

{{#gh-form-group errors=activeTag.errors property="meta_description"}}
{{#gh-form-group errors=activeTag.errors hasValidated=activeTag.hasValidated property="meta_description"}}
<label for="meta-description">Meta Description</label>
{{gh-textarea id="meta-description" name="meta_description" value=activeTagMetaDescriptionScratch focus-out="saveActiveTagMetaDescription"}}
{{gh-error-message errors=activeTag.errors property="meta_description"}}
Expand Down
4 changes: 2 additions & 2 deletions core/client/app/templates/setup/three.hbs
Expand Up @@ -6,8 +6,8 @@
<img class="gh-flow-faces" src="{{gh-path 'admin' 'img/users.png'}}" alt="" />

<form class="gh-flow-invite">
{{#gh-form-group errors=errors property="users"}}
<label>Enter one email address per line, we’ll handle the rest! <i class="icon-mail"></i></label>
{{#gh-form-group errors=errors hasValidated=hasValidated property="users"}}
<label for="users">Enter one email address per line, we’ll handle the rest! <i class="icon-mail"></i></label>
{{gh-textarea name="users" value=users required="required" focusOut=(action "validate")}}
{{/gh-form-group}}

Expand Down
8 changes: 4 additions & 4 deletions core/client/app/templates/setup/two.hbs
Expand Up @@ -8,28 +8,28 @@
<input style="display:none;" type="password" name="fakepasswordremembered"/>

{{gh-profile-image fileStorage=config.fileStorage email=validEmail setImage="setImage"}}
{{#gh-form-group errors=errors property="email"}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="email"}}
<label for="email-address">Email address</label>
<span class="input-icon icon-mail">
{{gh-trim-focus-input tabindex="1" type="email" name="email" placeholder="Eg. john@example.com" autocorrect="off" value=email focusOut=(action "preValidate" "email")}}
</span>
{{gh-error-message errors=errors property="email"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors property="name"}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="name"}}
<label for="full-name">Full name</label>
<span class="input-icon icon-user">
{{gh-input tabindex="2" type="text" name="name" placeholder="Eg. John H. Watson" autocorrect="off" value=name focusOut=(action "preValidate" "name")}}
</span>
{{gh-error-message errors=errors property="name"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors property="password"}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="password"}}
<label for="password">Password</label>
<span class="input-icon icon-lock">
{{gh-input tabindex="3" type="password" name="password" placeholder="At least 8 characters" autocorrect="off" value=password focusOut=(action "preValidate" "password")}}
</span>
{{gh-error-message errors=errors property="password"}}
{{/gh-form-group}}
{{#gh-form-group errors=errors property="blogTitle"}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="blogTitle"}}
<label for="blog-title">Blog title</label>
<span class="input-icon icon-content">
{{gh-input tabindex="4" type="text" name="blog-title" placeholder="Eg. The Daily Awesome" autocorrect="off" value=blogTitle focusOut=(action "preValidate" "blogTitle")}}
Expand Down
4 changes: 2 additions & 2 deletions core/client/app/templates/signin.hbs
Expand Up @@ -2,12 +2,12 @@
<div class="gh-flow-content-wrap">
<section class="gh-flow-content">
<form id="login" class="gh-signin" method="post" novalidate="novalidate">
{{#gh-form-group errors=model.errors property="identification"}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="identification"}}
<span class="input-icon icon-mail">
{{gh-trim-focus-input class="gh-input email" type="email" placeholder="Email Address" name="identification" autocapitalize="off" autocorrect="off" tabindex="1" value=model.identification}}
</span>
{{/gh-form-group}}
{{#gh-form-group errors=model.errors property="password"}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
<span class="input-icon icon-lock forgotten-wrap">
{{gh-input class="password" type="password" placeholder="Password" name="password" tabindex="2" value=model.password autocorrect="off"}}
{{#gh-spin-button class="forgotten-link btn btn-link" type="button" action="forgotten" tabindex="4" submitting=submitting autoWidth="true"}}Forgot?{{/gh-spin-button}}
Expand Down
6 changes: 3 additions & 3 deletions core/client/app/templates/signup.hbs
Expand Up @@ -12,21 +12,21 @@
<input style="display:none;" type="password" name="fakepasswordremembered"/>

{{gh-profile-image fileStorage=config.fileStorage email=validEmail setImage="setImage"}}
{{#gh-form-group errors=model.errors property="email"}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="email"}}
<label for="email-address">Email address</label>
<span class="input-icon icon-mail">
{{gh-input type="email" name="email" placeholder="Eg. john@example.com" enter=(action "signup") disabled="disabled" autocorrect="off" value=model.email focusOut=(action "handleEmail")}}
</span>
{{gh-error-message errors=model.errors property="email"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors property="name"}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="name"}}
<label for="full-name">Full name</label>
<span class="input-icon icon-user">
{{gh-trim-focus-input tabindex="1" type="text" name="name" placeholder="Eg. John H. Watson" enter=(action "signup") autocorrect="off" value=model.name focusOut=(action "validate" "name")}}
</span>
{{gh-error-message errors=model.errors property="name"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors property="password"}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
<label for="password">Password</label>
<span class="input-icon icon-lock">
{{gh-input tabindex="2" type="password" name="password" enter=(action "signup") autocorrect="off" value=model.password focusOut=(action "validate" "password")}}
Expand Down

0 comments on commit 9b6214b

Please sign in to comment.