Merge pull request #6017 from kevinansfield/finalize-debounced-gravatar

Finish changes in #5807 (debounced gravatar load in gh-profile-image)
This commit is contained in:
Hannah Wolfe 2015-11-02 18:14:53 +00:00
commit 54532e7e88
9 changed files with 136 additions and 91 deletions

View File

@ -5,36 +5,52 @@ import Ember from 'ember';
* A component to manage a user profile image. By default it just handles picture uploads, * A component to manage a user profile image. By default it just handles picture uploads,
* but if passed a bound 'email' property it will render the user's gravatar image * but if passed a bound 'email' property it will render the user's gravatar image
* *
* Example: {{gh-profile-image email=controllerEmailProperty setImage="controllerActionName"}} * Example: {{gh-profile-image email=controllerEmailProperty setImage="controllerActionName" debounce=500}}
* *
* @param {int} size The size of the image to render * @param {int} size The size of the image to render
* @param {String} email Reference to a bound email object if gravatar image behavior is desired. * @param {String} email Reference to a bound email object if gravatar image behavior is desired.
* @param {String} setImage The string name of the action on the controller to be called when an image is added. * @param {String|action} setImage The string name of the action on the controller to be called when an image is added.
* @property {Boolean} hasUploadedImage Whether or not the user has uploaded an image (whether or not to show the default image/gravatar image) * @param {int} debounce Period to wait after changes to email before attempting to load gravatar
* @property {String} defaultImage String containing the background-image css property of the default user profile image * @property {Boolean} hasUploadedImage Whether or not the user has uploaded an image (whether or not to show the default image/gravatar image)
* @property {String} imageBackground String containing the background-image css property with the gravatar url * @property {String} defaultImage String containing the background-image css property of the default user profile image
* @property {String} imageBackground String containing the background-image css property with the gravatar url
*/ */
export default Ember.Component.extend({ export default Ember.Component.extend({
email: '', email: '',
size: 90, size: 90,
debounce: 300,
validEmail: '',
hasUploadedImage: false, hasUploadedImage: false,
fileStorage: true, fileStorage: true,
ghostPaths: Ember.inject.service('ghost-paths'), ghostPaths: Ember.inject.service('ghost-paths'),
hasEmail: Ember.computed.notEmpty('email'), displayGravatar: Ember.computed.notEmpty('validEmail'),
defaultImage: Ember.computed('ghostPaths', function () { defaultImage: Ember.computed('ghostPaths', function () {
var url = this.get('ghostPaths.url').asset('/shared/img/user-image.png'); const url = this.get('ghostPaths.url').asset('/shared/img/user-image.png');
return `background-image: url(${url})`.htmlSafe(); return Ember.String.htmlSafe(`background-image: url(${url})`);
}), }),
imageBackground: Ember.computed('email', 'size', function () { trySetValidEmail: function () {
var email = this.get('email'), if (!this.get('isDestroyed')) {
size = this.get('size'), const email = this.get('email');
url; this.set('validEmail', validator.isEmail(email) ? email : '');
}
},
didReceiveAttrs: function (attrs) {
const timeout = parseInt(attrs.newAttrs.throttle || this.get('debounce'));
Ember.run.debounce(this, 'trySetValidEmail', timeout);
},
imageBackground: Ember.computed('validEmail', 'size', function () {
const email = this.get('validEmail'),
size = this.get('size');
if (email) { if (email) {
url = 'http://www.gravatar.com/avatar/' + md5(email) + '?s=' + size + '&d=blank'; let url = `http://www.gravatar.com/avatar/${md5(email)}?s=${size}&d=blank`;
return `background-image: url(${url})`.htmlSafe(); return Ember.String.htmlSafe(`background-image: url(${url})`);
} }
}), }),
@ -42,6 +58,9 @@ export default Ember.Component.extend({
var size = this.get('size'), var size = this.get('size'),
uploadElement = this.$('.js-file-input'); uploadElement = this.$('.js-file-input');
// Fire this immediately in case we're initialized with a valid email
this.trySetValidEmail();
// while theoretically the 'add' and 'processalways' functions could be // while theoretically the 'add' and 'processalways' functions could be
// added as properties of the hash passed to fileupload(), for some reason // added as properties of the hash passed to fileupload(), for some reason
// they needed to be placed in an on() call for the add method to work correctly // they needed to be placed in an on() call for the add method to work correctly
@ -59,11 +78,13 @@ export default Ember.Component.extend({
}, },
willDestroyElement: function () { willDestroyElement: function () {
this.$('.js-file-input').fileupload('destroy'); if (this.$('.js-file-input').data()['blueimp-fileupload']) {
this.$('.js-file-input').fileupload('destroy');
}
}, },
queueFile: function (e, data) { queueFile: function (e, data) {
var fileName = data.files[0].name; const fileName = data.files[0].name;
if ((/\.(gif|jpe?g|png|svg?z)$/i).test(fileName)) { if ((/\.(gif|jpe?g|png|svg?z)$/i).test(fileName)) {
this.sendAction('setImage', data); this.sendAction('setImage', data);
@ -71,7 +92,7 @@ export default Ember.Component.extend({
}, },
triggerPreview: function (e, data) { triggerPreview: function (e, data) {
var file = data.files[data.index]; const file = data.files[data.index];
if (file.preview) { if (file.preview) {
this.set('hasUploadedImage', true); this.set('hasUploadedImage', true);
// necessary jQuery code because file.preview is a raw DOM object // necessary jQuery code because file.preview is a raw DOM object

View File

@ -7,7 +7,6 @@ export default Ember.Controller.extend(ValidationEngine, {
blogTitle: null, blogTitle: null,
name: null, name: null,
email: '', email: '',
validEmail: '',
password: null, password: null,
image: null, image: null,
blogCreated: false, blogCreated: false,
@ -53,9 +52,6 @@ export default Ember.Controller.extend(ValidationEngine, {
preValidate: function (model) { preValidate: function (model) {
// Only triggers validation if a value has been entered, preventing empty errors on focusOut // Only triggers validation if a value has been entered, preventing empty errors on focusOut
if (this.get(model)) { if (this.get(model)) {
if (model === 'email') {
this.send('handleEmail');
}
this.validate({property: model}); this.validate({property: model});
} }
}, },
@ -119,13 +115,6 @@ export default Ember.Controller.extend(ValidationEngine, {
}, },
setImage: function (image) { setImage: function (image) {
this.set('image', image); this.set('image', image);
},
handleEmail: function () {
var self = this;
this.validate({property: 'email'}).then(function () {
self.set('validEmail', self.get('email'));
});
} }
} }
}); });

View File

@ -9,7 +9,6 @@ export default Ember.Controller.extend(ValidationEngine, {
submitting: false, submitting: false,
flowErrors: '', flowErrors: '',
image: null, image: null,
validEmail: '',
ghostPaths: Ember.inject.service('ghost-paths'), ghostPaths: Ember.inject.service('ghost-paths'),
config: Ember.inject.service(), config: Ember.inject.service(),
@ -88,13 +87,6 @@ export default Ember.Controller.extend(ValidationEngine, {
}, },
setImage: function (image) { setImage: function (image) {
this.set('image', image); this.set('image', image);
},
handleEmail: function () {
var self = this;
this.validate({property: 'email'}).then(function () {
self.set('validEmail', self.get('email'));
});
} }
} }
}); });

View File

@ -2,7 +2,7 @@
{{#unless hasUploadedImage}} {{#unless hasUploadedImage}}
<div class="placeholder-img" style={{defaultImage}}></div> <div class="placeholder-img" style={{defaultImage}}></div>
{{#if hasEmail}} {{#if displayGravatar}}
<div id="account-image" class="gravatar-img" style={{imageBackground}}> <div id="account-image" class="gravatar-img" style={{imageBackground}}>
<span class="sr-only">User image</span> <span class="sr-only">User image</span>
</div> </div>

View File

@ -7,7 +7,7 @@
<input style="display:none;" type="text" name="fakeusernameremembered"/> <input style="display:none;" type="text" name="fakeusernameremembered"/>
<input style="display:none;" type="password" name="fakepasswordremembered"/> <input style="display:none;" type="password" name="fakepasswordremembered"/>
{{gh-profile-image fileStorage=config.fileStorage email=validEmail setImage="setImage"}} {{gh-profile-image fileStorage=config.fileStorage email=email setImage="setImage"}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="email"}} {{#gh-form-group errors=errors hasValidated=hasValidated property="email"}}
<label for="email-address">Email address</label> <label for="email-address">Email address</label>
<span class="input-icon icon-mail"> <span class="input-icon icon-mail">

View File

@ -11,11 +11,11 @@
<input style="display:none;" type="text" name="fakeusernameremembered"/> <input style="display:none;" type="text" name="fakeusernameremembered"/>
<input style="display:none;" type="password" name="fakepasswordremembered"/> <input style="display:none;" type="password" name="fakepasswordremembered"/>
{{gh-profile-image fileStorage=config.fileStorage email=validEmail setImage="setImage"}} {{gh-profile-image fileStorage=config.fileStorage email=model.email setImage="setImage"}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="email"}} {{#gh-form-group errors=model.errors hasValidated=hasValidated property="email"}}
<label for="email-address">Email address</label> <label for="email-address">Email address</label>
<span class="input-icon icon-mail"> <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")}} {{gh-input type="email" name="email" placeholder="Eg. john@example.com" enter=(action "signup") disabled="disabled" autocorrect="off" value=model.email focusOut=(action "validate" "email")}}
</span> </span>
{{gh-error-message errors=model.errors property="email"}} {{gh-error-message errors=model.errors property="email"}}
{{/gh-form-group}} {{/gh-form-group}}

View File

@ -8,7 +8,7 @@
"ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3",
"ember-cli-test-loader": "0.1.3", "ember-cli-test-loader": "0.1.3",
"ember-data": "1.13.13", "ember-data": "1.13.13",
"ember-mocha": "0.8.4", "ember-mocha": "0.8.6",
"ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5",
"ember-resolver": "0.1.18", "ember-resolver": "0.1.18",
"fastclick": "1.0.6", "fastclick": "1.0.6",

View File

@ -0,0 +1,91 @@
/* jshint expr:true */
/* global md5 */
import { expect } from 'chai';
import {
describeComponent,
it
} from 'ember-mocha';
import hbs from 'htmlbars-inline-precompile';
import Ember from 'ember';
const {run} = Ember,
pathsStub = Ember.Service.extend({
url: {
api: function () {
return '';
},
asset: function (src) {
return src;
}
}
});
describeComponent(
'gh-profile-image',
'Integration: Component: gh-profile-image',
{
integration: true
},
function () {
beforeEach(function () {
this.register('service:ghost-paths', pathsStub);
this.inject.service('ghost-paths', {as: 'ghost-paths'});
});
it('renders', function () {
this.set('email', '');
this.render(hbs`
{{gh-profile-image email=email}}
`);
expect(this.$()).to.have.length(1);
});
it('immediately renders the gravatar if valid email supplied', function () {
let email = 'test@example.com',
expectedUrl = `http://www.gravatar.com/avatar/${md5(email)}?s=100&d=blank`;
this.set('email', email);
this.render(hbs`
{{gh-profile-image email=email size=100 debounce=300}}
`);
expect(this.$('.gravatar-img').attr('style'), 'gravatar image style')
.to.equal(`background-image: url(${expectedUrl})`);
});
it('throttles gravatar loading as email is changed', function (done) {
let email = 'test@example.com',
expectedUrl = `http://www.gravatar.com/avatar/${md5(email)}?s=100&d=blank`;
this.set('email', 'test');
this.render(hbs`
{{gh-profile-image email=email size=100 debounce=300}}
`);
expect(this.$('.gravatar-img').length, '.gravatar-img not shown for invalid email')
.to.equal(0);
run(() => {
this.set('email', email);
});
expect(this.$('.gravatar-img').length, '.gravatar-img not immediately changed on email change')
.to.equal(0);
Ember.run.later(this, function () {
expect(this.$('.gravatar-img').length, '.gravatar-img still not shown before throttle timeout')
.to.equal(0);
}, 250);
Ember.run.later(this, function () {
expect(this.$('.gravatar-img').attr('style'), '.gravatar-img style after timeout')
.to.equal(`background-image: url(${expectedUrl})`);
done();
}, 400);
});
}
);

View File

@ -1,48 +0,0 @@
/* jshint expr:true */
/* global md5 */
import { expect } from 'chai';
import {
describeComponent,
it
} from 'ember-mocha';
describeComponent(
'gh-profile-image',
'Unit: Component: gh-profile-image',
{
unit: true,
needs: ['service:ghost-paths']
},
function () {
it('renders', function () {
// creates the component instance
var component = this.subject();
expect(component._state).to.equal('preRender');
// renders the component on the page
this.render();
expect(component._state).to.equal('inDOM');
});
it('renders the gravatar image background if email is supplied', function () {
var component = this.subject(),
testEmail = 'test@example.com',
style, size;
component.set('email', testEmail);
this.render();
size = component.get('size');
style = 'url(http://www.gravatar.com/avatar/' + md5(testEmail) + '?s=' + size + '&d=blank)';
expect(component.$('#account-image').css('background-image')).to.equal(style);
});
it('doesn\'t render the gravatar image background if email isn\'t supplied', function () {
var component = this.subject();
this.render();
expect(component.$('#account-image').length).to.equal(0);
});
}
);