diff --git a/app/components/objective-manage-competency.js b/app/components/objective-manage-competency.js index da9d9d2624..844fa55888 100644 --- a/app/components/objective-manage-competency.js +++ b/app/components/objective-manage-competency.js @@ -1,79 +1,97 @@ import Ember from 'ember'; -import DS from 'ember-data'; -const { Component, computed } = Ember; -const { notEmpty, oneWay } = computed; +const { Component, RSVP, computed } = Ember; +const { Promise, all, filter} = RSVP; export default Component.extend({ objective: null, - programYear: oneWay('objective.programYear'), - showCompetencyList: notEmpty('programYear.competencies'), classNames: ['objective-manager', 'objective-manage-competency'], - competencies: computed('programYear.competencies.[]', 'objective.competency', function(){ - if(!this.get('programYear')){ - return []; - } - return DS.PromiseArray.create({ - promise: this.get('programYear.competencies') + schoolCompetencies: computed('programYear.program.school.competencies.[]', function(){ + return new Promise(resolve => { + this.get('programYear').then(programYear => { + programYear.get('program').then(program => { + program.get('school').then(school => { + school.get('competencies').then(competencies => { + resolve(competencies); + }); + }); + }); + }); }); }), - domains: computed('competencies.@each.domain', 'objective.competency', function(){ - var defer = Ember.RSVP.defer(); - var domainContainer = {}; - var domainIds = []; - var promises = []; - let domainProxy = Ember.ObjectProxy.extend({ - selectedCompetency: null, - subCompetencies: [], - selected: computed('subCompetencies.[]', 'selectedCompetency', function(){ - let selectedSubCompetencies = this.get('subCompetencies').filter(competencyProxy => { - return competencyProxy.get('id') === this.get('selectedCompetency.id'); - }); - return selectedSubCompetencies.length > 0; - }), + + programYear: computed('objective.programYears.[]', function(){ + return new Promise(resolve => { + const objective = this.get('objective'); + objective.get('programYears').then(programYears => { + if (programYears.length) { + let programYear = programYears.get('firstObject'); + resolve(programYear); + } else { + resolve(null); + } + }); }); - let competencyProxy = Ember.ObjectProxy.extend({ - selectedCompetency: null, - selected: computed('content', 'selectedCompetency', function(){ - return this.get('content.id') === this.get('selectedCompetency.id'); - }), + }), + + competencies: computed('programYear.competencies.[]', function(){ + return new Promise(resolve => { + this.get('programYear').then(programYear => { + programYear.get('competencies').then(competencies => { + resolve(competencies); + }); + }); }); - this.get('competencies').forEach((competency) =>{ - promises.pushObject(competency.get('domain').then( - domain => { - if(!domainContainer.hasOwnProperty(domain.get('id'))){ - domainIds.pushObject(domain.get('id')); - domainContainer[domain.get('id')] = domainProxy.create({ - content: domain, - selectedCompetency: this.get('objective.competency'), - subCompetencies: [], + }), + + competenciesWithSelectedChildren: computed('schoolCompetencies.[]', 'objective.competency', function(){ + return new Promise(resolve => { + const objective = this.get('objective'); + objective.get('competency').then(selectedCompetency => { + if (selectedCompetency) { + this.get('schoolCompetencies').then(competencies => { + filter(competencies.toArray(), (competency => { + return new Promise(resolve => { + competency.get('treeChildren').then(children => { + let selectedChildren = children.filter(competency => selectedCompetency.get('id') === competency.get('id')); + resolve(selectedChildren.length > 0); + }); + }); + })).then(competenciesWithSelectedChildren => { + resolve(competenciesWithSelectedChildren); }); - } - if(competency.get('id') !== domain.get('id')){ - var subCompetencies = domainContainer[domain.get('id')].get('subCompetencies'); - if(!subCompetencies.contains(competency)){ - subCompetencies.pushObject(competencyProxy.create({ - content: competency, - selectedCompetency: this.get('objective.competency') - })); - subCompetencies.sortBy('title'); - } - } + }); + } else { + resolve([]); } - )); + }); }); - Ember.RSVP.all(promises).then(function(){ - var domains = domainIds.map(function(id){ - return domainContainer[id]; - }).filter( - domain => domain.get('subCompetencies').length > 0 - ).sortBy('title'); - defer.resolve(domains); + }), + + domains: computed('competencies.[]', function(){ + return new Promise(resolve => { + this.get('competencies').then(competencies => { + all(competencies.mapBy('domain')).then(domains => { + resolve(domains.uniq()); + }); + }); }); + }), - return DS.PromiseArray.create({ - promise: defer.promise + domainsWithNoChildren: computed('domains.[]', function(){ + return new Promise(resolve => { + this.get('domains').then(domains => { + filter(domains.toArray(), (competency => { + return new Promise(resolve => { + competency.get('children').then(children => { + resolve(children.length === 0); + }); + }); + })).then(domainsWithNoChildren => { + resolve(domainsWithNoChildren); + }); + }); }); }), actions: { diff --git a/app/components/programyear-competencies.js b/app/components/programyear-competencies.js index 7c25707bb8..92e91f5ada 100644 --- a/app/components/programyear-competencies.js +++ b/app/components/programyear-competencies.js @@ -1,65 +1,115 @@ import Ember from 'ember'; +import { task, timeout } from 'ember-concurrency'; -const { Component, computed } = Ember; +const { Component, RSVP, computed } = Ember; +const { Promise, all, filter } = RSVP; export default Component.extend({ + init(){ + this._super(...arguments); + this.get('loadSelectedCompetencies').perform(); + }, + didUpdateAttrs(){ + this._super(...arguments); + this.get('loadSelectedCompetencies').perform(); + }, programYear: null, + isManaging: null, classNames: ['programyear-competencies'], - isManaging: false, isSaving: false, - bufferCompetencies: [], + selectedCompetencies: [], - showCollapsible: computed('isManaging', 'programYear.competencies.[]', function () { - const isManaging = this.get('isManaging'); - const competencies = this.get('programYear.competencies'); - return !isManaging && competencies.get('length'); - }), + loadSelectedCompetencies: task(function * (){ + const programYear = this.get('programYear'); + if (programYear){ + let selectedCompetencies = yield programYear.get('competencies'); + this.set('selectedCompetencies', selectedCompetencies.toArray()); + } else { + yield timeout(1000); + } + }).restartable(), - actions: { - manage: function(){ - var self = this; - this.get('programYear.competencies').then(function(competencies){ - self.set('bufferCompetencies', competencies.toArray()); - self.set('isManaging', true); + save: task(function * () { + yield timeout(10); + let programYear = this.get('programYear'); + let selectedCompetencies = this.get('selectedCompetencies'); + programYear.set('competencies', selectedCompetencies); + try { + yield programYear.save(); + } finally { + this.get('flashMessages').success('general.savedSuccessfully'); + this.get('setIsManaging')(false); + this.get('expand')(); + } + + }).drop(), + + competencies: computed('programYear.program.school.competencies.[]', function(){ + return new Promise(resolve => { + const programYear = this.get('programYear') + programYear.get('program').then(program => { + program.get('school').then(school => { + school.get('competencies').then(competencies => { + resolve(competencies); + }); + }); }); - }, - save: function(){ - this.set('isSaving', true); - let programYear = this.get('programYear'); - programYear.get('competencies').then(competencies => { - competencies.clear(); - this.get('bufferCompetencies').forEach(competency => { - competency.get('programYears').addObject(programYear); - competencies.addObject(competency); + }); + }), + + domains: computed('competencies.[]', function(){ + return new Promise(resolve => { + this.get('competencies').then(competencies => { + all(competencies.mapBy('domain')).then(domains => { + resolve(domains.uniq()); }); - programYear.save().then(()=> { - this.set('isSaving', false); - this.set('isManaging', false); + }); + }); + }), + + competenciesWithSelectedChildren: computed('competencies.[]', 'selectedCompetencies.[]', function(){ + const selectedCompetencies = this.get('selectedCompetencies'); + return new Promise(resolve => { + this.get('competencies').then(competencies => { + filter(competencies.toArray(), (competency => { + return new Promise(resolve => { + competency.get('treeChildren').then(children => { + let selectedChildren = children.filter(competency => selectedCompetencies.contains(competency)); + resolve(selectedChildren.length > 0); + }); + }); + })).then(competenciesWithSelectedChildren => { + resolve(competenciesWithSelectedChildren); }); }); - }, + }); + }), + + actions: { cancel: function(){ - this.set('bufferCompetencies', []); - this.set('isManaging', false); + this.get('loadSelectedCompetencies').perform(); + this.get('setIsManaging')(false); }, addCompetencyToBuffer: function(competency){ - this.get('bufferCompetencies').addObject(competency); + let selectedCompetencies = this.get('selectedCompetencies').toArray(); + selectedCompetencies.addObject(competency); competency.get('children').then(children => { - this.get('bufferCompetencies').addObjects(children); + selectedCompetencies.addObjects(children.toArray()); }); + this.set('selectedCompetencies', selectedCompetencies); }, removeCompetencyFromBuffer: function(competency){ - this.get('bufferCompetencies').removeObject(competency); + let selectedCompetencies = this.get('selectedCompetencies').toArray(); + selectedCompetencies.removeObject(competency); competency.get('children').then(children => { - children.forEach(child => { - this.get('bufferCompetencies').removeObject(child); - }); + selectedCompetencies.removeObjects(children.toArray()); }); + this.set('selectedCompetencies', selectedCompetencies); }, collapse(){ this.get('programYear.competencies').then(competencies => { if (competencies.get('length')) { - this.attrs.collapse(); + this.get('collapse')(); } }); }, diff --git a/app/components/programyear-competency-manager.js b/app/components/programyear-competency-manager.js deleted file mode 100644 index a81816f128..0000000000 --- a/app/components/programyear-competency-manager.js +++ /dev/null @@ -1,107 +0,0 @@ -import Ember from 'ember'; -import DS from 'ember-data'; - -const { Component, computed } = Ember; - -export default Component.extend({ - filter: '', - availableCompetencies: [], - selectedCompetencies: [], - tagName: 'section', - classNames: ['detail-block'], - filteredCompetencies: computed('availableCompetencies.[]', 'selectedCompetencies.[]', function(){ - return this.get('availableCompetencies').filter( - competency => { - return !this.get('selectedCompetencies').contains(competency); - } - ); - }), - filteredDomains: computed('filteredCompetencies.@each.domain', function(){ - var defer = Ember.RSVP.defer(); - var domainContainer = {}; - var domainIds = []; - var promises = []; - this.get('filteredCompetencies').forEach(function(competency){ - promises.pushObject(competency.get('domain').then( - domain => { - if(!domainContainer.hasOwnProperty(domain.get('id'))){ - domainIds.pushObject(domain.get('id')); - domainContainer[domain.get('id')] = Ember.ObjectProxy.create({ - content: domain, - subCompetencies: [] - }); - } - if(competency.get('id') !== domain.get('id')){ - var subCompetencies = domainContainer[domain.get('id')].get('subCompetencies'); - if(!subCompetencies.contains(competency)){ - subCompetencies.pushObject(competency); - subCompetencies.sortBy('title'); - } - } - } - )); - }); - Ember.RSVP.all(promises).then(function(){ - var domains = domainIds.map(function(id){ - return domainContainer[id]; - }).filter( - domain => domain.get('subCompetencies').length > 0 - ).sortBy('title'); - defer.resolve(domains); - }); - - return DS.PromiseArray.create({ - promise: defer.promise - }); - }), - selectedDomains: computed('selectedCompetencies.@each.domain', function(){ - var defer = Ember.RSVP.defer(); - var domainContainer = {}; - var domainIds = []; - var promises = []; - this.get('selectedCompetencies').forEach(function(competency){ - promises.pushObject(competency.get('domain').then( - domain => { - if(!domainContainer.hasOwnProperty(domain.get('id'))){ - domainIds.pushObject(domain.get('id')); - domainContainer[domain.get('id')] = Ember.ObjectProxy.create({ - content: domain, - subCompetencies: [] - }); - } - if(competency.get('id') !== domain.get('id')){ - var subCompetencies = domainContainer[domain.get('id')].get('subCompetencies'); - if(!subCompetencies.contains(competency)){ - subCompetencies.pushObject(competency); - subCompetencies.sortBy('title'); - } - } - } - )); - }); - Ember.RSVP.all(promises).then(function(){ - var domains = domainIds.map(function(id){ - return domainContainer[id]; - }).sortBy('title'); - defer.resolve(domains); - }); - - return DS.PromiseArray.create({ - promise: defer.promise - }); - }), - actions: { - removeCompetency: function(competency){ - this.sendAction('remove', competency); - }, - removeDomain: function(proxy){ - this.sendAction('remove', proxy.get('content')); - }, - addCompetency: function(competency){ - this.sendAction('add', competency); - }, - addDomain: function(proxy){ - this.sendAction('add', proxy.get('content')); - } - } -}); diff --git a/app/controllers/program-year/index.js b/app/controllers/program-year/index.js index 3dd070c39f..1e87a9088e 100644 --- a/app/controllers/program-year/index.js +++ b/app/controllers/program-year/index.js @@ -5,10 +5,16 @@ const { controller } = inject; const { alias, not } = computed; export default Controller.extend({ - queryParams: ['pyObjectiveDetails', 'pyTaxonomyDetails', 'pyCompetencyDetails'], + queryParams: [ + 'pyObjectiveDetails', + 'pyTaxonomyDetails', + 'pyCompetencyDetails', + 'managePyCompetencies', + ], pyObjectiveDetails: false, pyTaxonomyDetails: false, pyCompetencyDetails: false, + managePyCompetencies: false, programYearController: controller('programYear'), programController: controller('program'), diff --git a/app/models/competency.js b/app/models/competency.js index dd583115de..f305d56dbf 100644 --- a/app/models/competency.js +++ b/app/models/competency.js @@ -1,17 +1,19 @@ import DS from 'ember-data'; import Ember from 'ember'; -const { computed } = Ember; +const { computed, RSVP } = Ember; const { empty, not } = computed; +const { Promise, all } = RSVP; +const { Model, attr, belongsTo, hasMany } = DS; -export default DS.Model.extend({ - title: DS.attr('string'), - school: DS.belongsTo('school', {async: true}), - objectives: DS.hasMany('objective', {async: true}), - parent: DS.belongsTo('competency', {async: true, inverse: 'children'}), - children: DS.hasMany('competency', {async: true, inverse: 'parent'}), - aamcPcrses: DS.hasMany('aamc-pcrs', {async: true}), - programYears: DS.hasMany('program-year', {async: true}), +export default Model.extend({ + title: attr('string'), + school: belongsTo('school', {async: true}), + objectives: hasMany('objective', {async: true}), + parent: belongsTo('competency', {async: true, inverse: 'children'}), + children: hasMany('competency', {async: true, inverse: 'parent'}), + aamcPcrses: hasMany('aamc-pcrs', {async: true}), + programYears: hasMany('program-year', {async: true}), isDomain: empty('parent.content'), isNotDomain: not('isDomain'), domain: computed('parent', 'parent.domain', function(){ @@ -34,4 +36,23 @@ export default DS.Model.extend({ promise: promise }); }), + treeChildren: computed('children.[]', function(){ + return new Promise(resolve => { + let rhett = []; + this.get('children').then(children => { + rhett.pushObjects(children.toArray()); + all(children.mapBy('treeChildren')).then(trees => { + let competencies = trees.reduce(function(array, set){ + return array.pushObjects(set.toArray()); + }, []); + rhett.pushObjects(competencies); + rhett = rhett.uniq().filter(function(item){ + return item != null; + }); + resolve(rhett); + }) + }) + }); + + }) }); diff --git a/app/styles/components/_objectivemanager.scss b/app/styles/components/_objectivemanager.scss index c184221261..5c2efb27a9 100644 --- a/app/styles/components/_objectivemanager.scss +++ b/app/styles/components/_objectivemanager.scss @@ -19,10 +19,11 @@ li { list-style-position: outside; - margin-left: 1.5em; - margin-right: 1.5em; padding: .2em 0 .3em 1.5em; - text-indent: -1.2em; + + ul { + margin-left: 1.5em; + } } } diff --git a/app/styles/newcomponents.scss b/app/styles/newcomponents.scss index 08af097de6..6c4c083f06 100644 --- a/app/styles/newcomponents.scss +++ b/app/styles/newcomponents.scss @@ -37,3 +37,4 @@ @import 'newcomponents/session-copy'; @import 'newcomponents/instructorgroup-header'; @import 'newcomponents/programyear-list'; +@import 'newcomponents/programyear-competencies'; diff --git a/app/styles/newcomponents/programyear-competencies.scss b/app/styles/newcomponents/programyear-competencies.scss new file mode 100644 index 0000000000..9d7368e21d --- /dev/null +++ b/app/styles/newcomponents/programyear-competencies.scss @@ -0,0 +1,35 @@ +.programyear-competencies { + @include detail-container($ilios-orange); + + .title { + @include detail-container-title; + } + + .programyear-competencies-actions { + @include detail-container-actions; + } + + .programyear-competencies-content { + @include detail-container-content; + } + + .competency-list, + .managed-competency-list { + @include ilios-list-tree; + border: 1px solid $input-box-grey; + border-radius: 3px; + margin: 2em; + + li { + font-weight: bold; + } + + ul { + margin-left: 1em; + + li { + font-weight: normal; + } + } + } +} diff --git a/app/templates/components/objective-manage-competency.hbs b/app/templates/components/objective-manage-competency.hbs index 4e37e6b7d9..c5e25123be 100644 --- a/app/templates/components/objective-manage-competency.hbs +++ b/app/templates/components/objective-manage-competency.hbs @@ -1,26 +1,42 @@
diff --git a/app/templates/components/programyear-competencies.hbs b/app/templates/components/programyear-competencies.hbs
index 9a38549c23..ce2af0e24b 100644
--- a/app/templates/components/programyear-competencies.hbs
+++ b/app/templates/components/programyear-competencies.hbs
@@ -1,45 +1,87 @@
-
- {{#each programYear.domains as |domain|}}
-
- {{#each domain.subCompetencies as |competency|}}
-
-
+ {{#if isManaging}}
+ {{#each (sort-by 'title' (await domains)) as |domain|}}
+
- {{/liquid-if}}
-
+ {{#each (sort-by 'title' (await competencies)) as |competency|}}
+ {{#if (contains competency (await domain.treeChildren))}}
+
+
+ {{#each (sort-by 'title' (await competencies)) as |competency|}}
+ {{#if (and (contains competency (await domain.treeChildren)) (contains competency.id (map-by 'id' selectedCompetencies)))}}
+
+ {{t 'general.selected'}}
-
- {{#each selectedDomains as |domain|}}
-
-
- {{#each domain.subCompetencies as |competency|}}
-
- {{t 'general.available'}}
-
- {{#each filteredDomains as |domain|}}
-
-
- {{#each domain.subCompetencies as |competency|}}
-
-