Skip to content

Commit

Permalink
Merge pull request #1854 from jrjohnson/1845-comtitle
Browse files Browse the repository at this point in the history
Validate competency title
  • Loading branch information
stopfstedt authored Jul 14, 2016
2 parents dbae80c + b4539b5 commit 5c7045f
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 74 deletions.
58 changes: 58 additions & 0 deletions app/components/competency-title-editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Ember from 'ember';
import { validator, buildValidations } from 'ember-cp-validations';
import ValidationErrorDisplay from 'ilios/mixins/validation-error-display';
import { task } from 'ember-concurrency';

const { Component, RSVP, isPresent } = Ember;
const { Promise } = RSVP;

const Validations = buildValidations({
title: [
validator('presence', true),
validator('length', {
max: 200
}),
],
});

export default Component.extend(Validations, ValidationErrorDisplay, {
didReceiveAttrs(){
this._super(...arguments);
const competency = this.get('competency');
if (isPresent(competency)) {
this.set('title', competency.get('title'));
}
},
title: null,
competency: null,
classNames: ['competency-title-editor'],
tagName: 'span',
save: task(function * (){
this.send('addErrorDisplayFor', 'title');
let {validations} = yield this.validate();

return new Promise((resolve, reject) => {
if (validations.get('isInvalid')) {
reject();
} else {
const competency = this.get('competency');
const title = this.get('title');
if (isPresent(competency)) {
competency.set('title', title);
}
this.send('clearErrorDisplay');
resolve();
}
});

}),
actions: {
revert() {
this.set('title', null);
const competency = this.get('competency');
if (isPresent(competency)) {
this.set('title', competency.get('title'));
}
},
}
});
32 changes: 32 additions & 0 deletions app/components/new-competency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Ember from 'ember';
import { validator, buildValidations } from 'ember-cp-validations';
import ValidationErrorDisplay from 'ilios/mixins/validation-error-display';
import { task, timeout } from 'ember-concurrency';

const { Component } = Ember;

const Validations = buildValidations({
title: [
validator('presence', true),
validator('length', {
max: 200
}),
],
});

export default Component.extend(Validations, ValidationErrorDisplay, {
title: null,
classNames: ['new-competency'],
save: task(function * (){
this.send('addErrorDisplayFor', 'title');
let {validations} = yield this.validate();
if (validations.get('isInvalid')) {
return;
}
yield timeout(10);
const title = this.get('title');
yield this.get('add')(title);
this.send('clearErrorDisplay');
this.set('title', null);
})
});
4 changes: 2 additions & 2 deletions app/components/school-competencies-expanded.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default Component.extend({
}
});
},
addCompetencyToBuffer(title, domain){
addCompetencyToBuffer(domain, title){
let competency = this.get('store').createRecord('competency', {title});
if (isPresent(domain)) {
competency.set('parent', domain);
Expand Down Expand Up @@ -68,8 +68,8 @@ export default Component.extend({
});
bufferedCompetencies.filterBy('isNew').forEach(competency => {
competency.set('school', school);
promises.pushObject(competency.save());
});
promises.pushObjects(bufferedCompetencies.filterBy('hasDirtyAttributes').invoke('save'));
schoolCompetencies.clear();
bufferedCompetencies.forEach(competency=>{
schoolCompetencies.pushObject(competency);
Expand Down
41 changes: 2 additions & 39 deletions app/components/school-competencies-manager.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import Ember from 'ember';

const { Component, computed, isPresent, isEmpty } = Ember;
const { sort } = computed;
const { Component, computed, isEmpty } = Ember;

export default Component.extend({
competencies: [],
sortCompetenciesBy: ['title'],
sortedCompetencies: sort('competencies', 'sortCompetenciesBy'),
newDomainValue: null,

domains: computed('competencies.[]', function(){
let competencies = this.get('competencies');
Expand All @@ -16,7 +12,7 @@ export default Component.extend({
}
let domains = competencies.filterBy('isDomain');
let objs = domains.uniq().map(domain => {
let domainCompetencies = competencies.filter(competency => competency.get('parent.id') === domain.get('id'));
let domainCompetencies = competencies.filter(competency => competency.belongsTo('parent').id() === domain.get('id'));
return {
domain,
competencies: domainCompetencies.sortBy('title')
Expand All @@ -26,41 +22,8 @@ export default Component.extend({
return objs.sortBy('domain.title');
}),
actions: {
createNewDomain(){
let value = this.get('newDomainValue');
if (isPresent(value)) {
this.attrs.add(value);
this.set('newDomainValue', null);
}
},
createNewCompetencyFromButton(e){
let domainId = parseInt(e.target.value);
let value = this.$(e.target).parent().find('input').val();
let objs = this.get('domains');
let obj = objs.find(obj => parseInt(obj.domain.get('id')) === domainId);
let domain = obj.domain;
if (isPresent(value) && isPresent(domain)) {
this.attrs.add(value, domain);
}

},
createNewCompetencyFromInput(e){
if (e.keyCode === 13) {
let value = e.target.value;
let domainId = parseInt(this.$(e.target).parent().find('button').val());

let objs = this.get('domains');
let obj = objs.find(obj => parseInt(obj.domain.get('id')) === domainId);
let domain = obj.domain;
if (isPresent(value) && isPresent(domain)) {
this.attrs.add(value, domain);
}
}

},
changeCompetencyTitle(value, competency){
competency.set('title', value);
competency.save();
}
}
});
18 changes: 18 additions & 0 deletions app/templates/components/competency-title-editor.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{#editable-field
value=title
save=(perform save)
close=(action 'revert')
as |isSaving save close|
}}
{{one-way-input
value=title
update=(action (mut title))
onenter=save
onescape=close
disabled=isSaving
focusOut=(action 'addErrorDisplayFor' 'title')
}}
{{/editable-field}}
{{#if (and (v-get this 'title' 'isInvalid') (is-in showErrorsFor 'title'))}}
<span class="validation-error-message">{{v-get this 'title' 'message'}}</span>
{{/if}}
19 changes: 19 additions & 0 deletions app/templates/components/new-competency.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{one-way-input
value=title
update=(action (mut title))
onenter=(perform save)
onescape=(pipe (action 'removeErrorDisplayFor' 'title') (action (mut title) ''))
focusOut=(action 'addErrorDisplayFor' 'title')
keyDown=(action 'addErrorDisplayFor' 'title')
placeholder=(t 'general.title')
}}
<button class='save text' {{action (perform save)}}>
{{#if save.isRunning}}
{{fa-icon 'spinner' spin=true}}
{{else}}
{{t 'general.add'}}
{{/if}}
</button>
{{#if (and (v-get this 'title' 'isInvalid') (is-in showErrorsFor 'title'))}}
<span class="validation-error-message">{{v-get this 'title' 'message'}}</span>
{{/if}}
20 changes: 5 additions & 15 deletions app/templates/components/school-competencies-manager.hbs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{{#each domains as |obj|}}
<div class='hierarchical-list-manager'>
{{inplace-text tagName='h4' value=obj.domain.title save='changeCompetencyTitle' condition=obj.domain}}
{{competency-title-editor competency=obj.domain}}
{{#if (eq obj.competencies.length 0)}}
{{fa-icon 'trash' class='remove clickable' click=(action this.attrs.remove obj.domain)}}
{{/if}}
<ul>
{{#each obj.competencies as |competency|}}
<li>
{{inplace-text value=competency.title save='changeCompetencyTitle' condition=competency}}
{{competency-title-editor competency=competency}}
{{#if (eq competency.objectives.length 0)}}
{{fa-icon 'trash' class='remove clickable' click=(action this.attrs.remove competency)}}
{{else}}
Expand All @@ -16,21 +16,11 @@
</li>
{{/each}}
{{#if obj.domain.id}}
<div>
<input onkeyup={{action (action 'createNewCompetencyFromInput')}} />
<button class='save text' value={{obj.domain.id}} onclick={{action (action 'createNewCompetencyFromButton')}}>{{t 'general.add'}}</button>
</div>
{{new-competency add=(action add obj.domain)}}
{{/if}}
</ul>
</div>
{{/each}}

<div>
<label>{{t 'schools.newDomain'}}</label>
{{one-way-input
value=newDomainValue
update=(action (mut newDomainValue))
onenter=(action 'createNewDomain')
}}
<button class='save text' {{action 'createNewDomain'}}>{{t 'general.add'}}</button>
</div>
<h5>{{t 'schools.newDomain'}}</h5>
{{new-competency add=(action add null)}}
34 changes: 34 additions & 0 deletions tests/integration/components/competency-title-editor-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Ember from 'ember';
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import wait from 'ember-test-helpers/wait';

const { Object } = Ember;

moduleForComponent('competency-title-editor', 'Integration | Component | competency title editor', {
integration: true
});

test('validation errors do not show up initially', function(assert) {
assert.expect(1);
let competency = Object.create({
title: 'test'
});
this.set('competency', competency);
this.render(hbs`{{competency-title-editor competency=competency}}`);
assert.equal(this.$('.validation-error-message').length, 0);
});

test('validation errors show up when saving', function(assert) {
assert.expect(1);
let competency = Object.create({
title: 'test'
});
this.set('competency', competency);
this.render(hbs`{{competency-title-editor competency=competency}}`);
this.$('.content span:eq(0)').click();
this.$('input').val('').change();
this.$('button.done').click();
assert.equal(this.$('.validation-error-message').length, 1);
return wait();
});
42 changes: 42 additions & 0 deletions tests/integration/components/new-competency-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import wait from 'ember-test-helpers/wait';

moduleForComponent('new-competency', 'Integration | Component | new competency', {
integration: true
});

test('it renders', function(assert) {
this.render(hbs`{{new-competency}}`);

assert.equal(this.$('input').length, 1);
assert.equal(this.$('button').text().trim(), 'Add');
});

test('save', function(assert) {
assert.expect(1);
this.set('add', (value) => {
assert.equal(value, 'new co');
});
this.render(hbs`{{new-competency add=(action add)}}`);
this.$('input').val('new co').change();
this.$('button').click();

return wait();
});

test('validation errors do not show up initially', function(assert) {
assert.expect(1);

this.render(hbs`{{new-competency}}`);
assert.equal(this.$('.validation-error-message').length, 0);
});

test('validation errors show up when saving', function(assert) {
assert.expect(1);

this.render(hbs`{{new-competency}}`);
this.$('button.save').click();
assert.equal(this.$('.validation-error-message').length, 1);
return wait();
});
Loading

0 comments on commit 5c7045f

Please sign in to comment.