Merge pull request #52 from thermokarst/refactor-strains

Refactor strains
This commit is contained in:
Matthew Dillon 2015-11-11 12:36:56 -07:00
commit 7497bacc4b
33 changed files with 690 additions and 439 deletions

View file

@ -24,4 +24,19 @@ export function testConfig() {
this.post('/characteristics'); this.post('/characteristics');
this.get('/characteristics/:id'); this.get('/characteristics/:id');
this.put('/characteristics/:id'); this.put('/characteristics/:id');
this.get('/strains', function(db /*, request*/) {
return {
strains: db.strains,
species: db.species,
};
});
this.post('/strains');
this.get('/strains/:id', function(db, request) {
return {
strain: db.strains.find(request.params.id),
species: db.species, // Just send back everything we've got
};
});
this.put('/strains/:id');
} }

View file

@ -0,0 +1,17 @@
import Mirage, { faker } from 'ember-cli-mirage';
export default Mirage.Factory.extend({
measurements: [],
characteristics: [],
species: 0,
strainName() { return faker.lorem.words().join(' '); },
typeStrain: faker.random.boolean(),
accessionNumbers() { return faker.lorem.words().join(' '); },
genbank() { return faker.lorem.words().join(' '); },
wholeGenomeSequence() { return faker.lorem.words().join(' '); },
isolatedFrom: faker.lorem.sentences(),
notes: faker.lorem.sentences(),
totalMeasurements: 0,
sortOrder: faker.random.number(),
canEdit: faker.random.boolean(),
});

View file

@ -13,17 +13,12 @@ export default Mixin.create({
const fallbackRoute = this.get('fallbackRouteSave'); const fallbackRoute = this.get('fallbackRouteSave');
model.setProperties(properties); model.setProperties(properties);
model.save().then((model) => {
if (model.get('hasDirtyAttributes')) { this.get('flashMessages').clearMessages();
model.save().then((model) => {
this.get('flashMessages').clearMessages();
this.transitionToRoute(fallbackRoute, model);
}, () => {
ajaxError(model.get('errors'), this.get('flashMessages'));
});
} else {
this.transitionToRoute(fallbackRoute, model); this.transitionToRoute(fallbackRoute, model);
} }, () => {
ajaxError(model.get('errors'), this.get('flashMessages'));
});
}, },
cancel: function() { cancel: function() {

View file

@ -25,6 +25,10 @@ export default DS.Model.extend({
return Ember.String.htmlSafe(`${this.get('strainName')}${type}`); return Ember.String.htmlSafe(`${this.get('strainName')}${type}`);
}.property('strainName', 'typeStrain').readOnly(), }.property('strainName', 'typeStrain').readOnly(),
fullName: Ember.computed('species', 'strainName', function() {
return `${this.get('species.speciesName')} ${this.get('strainNameMU')}`;
}),
fullNameMU: function() { fullNameMU: function() {
return Ember.String.htmlSafe(`<em>${this.get('species.speciesName')}</em> ${this.get('strainNameMU')}`); return Ember.String.htmlSafe(`<em>${this.get('species.speciesName')}</em> ${this.get('strainNameMU')}`);
}.property('species', 'strainNameMU').readOnly(), }.property('species', 'strainNameMU').readOnly(),

View file

@ -1,12 +1,12 @@
import Ember from 'ember'; import Ember from 'ember';
import SetupMetaData from '../../../../../mixins/setup-metadata'; import SetupMetaData from '../../../../../mixins/setup-metadata';
const { Component } = Ember; const { Component, computed: { sort } } = Ember;
export default Component.extend(SetupMetaData, { export default Component.extend(SetupMetaData, {
species: null, species: null,
sortParams: ['speciesName', 'strainCount'], sortParams: ['speciesName', 'strainCount'],
sortedSpecies: Ember.computed.sort('species', 'sortParams'), sortedSpecies: sort('species', 'sortParams'),
}); });

View file

@ -1,31 +1,36 @@
import Ember from 'ember'; import Ember from 'ember';
import SaveModel from '../../../../mixins/save-model';
import ajaxError from '../../../../utils/ajax-error'; import ajaxError from '../../../../utils/ajax-error';
export default Ember.Controller.extend({ const { Controller } = Ember;
actions: {
save: function() {
let strain = this.get('strain');
if (strain.get('hasDirtyAttributes')) { export default Controller.extend(SaveModel, {
strain.save().then((strain) => { // Required for SaveModel mixin
this.transitionToRoute('protected.strains.show', strain); fallbackRouteSave: 'protected.strains.show',
}, () => { fallbackRouteCancel: 'protected.strains.show',
ajaxError(strain.get('errors'), this.get('flashMessages'));
}); actions: {
} else { addCharacteristic: function() {
strain.destroyRecord().then(() => { return this.store.createRecord('measurement', {
this.transitionToRoute('protected.strains.show', strain); characteristic: this.store.createRecord('characteristic', { sortOrder: -999 }),
}); });
}
}, },
cancel: function() { saveMeasurement: function(measurement, properties) {
let strain = this.get('strain'); measurement.setProperties(properties);
measurement.save().then(() => {
this.get('flashMessages').clearMessages();
}, () => {
ajaxError(measurement.get('errors'), this.get('flashMessages'));
});
},
strain.get('errors').clear(); deleteMeasurement: function(measurement) {
strain.rollbackAttributes(); const characteristic = measurement.get('characteristic');
if (characteristic.get('isNew')) {
this.transitionToRoute('protected.strains.show', strain); characteristic.destroyRecord();
}
measurement.destroyRecord();
}, },
}, },

View file

@ -1,35 +1,44 @@
import Ember from 'ember'; import Ember from 'ember';
import ElevatedAccess from '../../../../mixins/elevated-access';
export default Ember.Route.extend({ const { Route } = Ember;
currentUser: Ember.inject.service('session-account'),
beforeModel: function(transition) { export default Route.extend(ElevatedAccess, {
this._super(transition); // Required for ElevatedAccess mixin
this.get('currentUser.account').then((user) => { fallbackRouteBefore: 'protected.strains.index',
if (user.get('isReader')) { fallbackRouteAfter: 'protected.strains.show',
this.transitionTo('protected.strains.index');
}
});
},
model: function(params) { model: function(params) {
return Ember.RSVP.hash({ return Ember.RSVP.hash({
strain: this.store.find('strain', params.strain_id), strain: this.store.findRecord('strain', params.strain_id),
species: this.store.findAll('species'), // Need for dropdown species: this.store.findAll('species'), // Need for dropdown
characteristics: this.store.findAll('characteristic'), // Need for dropdown
}); });
}, },
// Overriding afterModel because of RSVP hash
afterModel: function(models) { afterModel: function(models) {
if (!models.strain.get('canEdit')) { if (!models.strain.get('isNew') && !models.strain.get('canEdit')) {
this.transitionTo('strains.show', models.strain.get('id')); this.transitionTo(this.get('fallbackRouteAfter'), models.strain.get('id'));
} }
}, },
// Setting up controller because of RSVP hash
setupController: function(controller, models) { setupController: function(controller, models) {
controller.setProperties(models); controller.set('model', models.strain);
this.get('currentUser.account').then((user) => { controller.set('speciesList', models.species);
controller.set('metaData', user.get('metaData')); controller.set('allCharacteristics', models.characteristics);
});
}, },
actions: {
// Overriding willTransition because of RSVP hash
willTransition: function(/*transition*/) {
const controller = this.get('controller');
const model = controller.get('model');
if (model.get('isNew')) {
model.destroyRecord();
}
},
},
}); });

View file

@ -1,8 +1,11 @@
{{ {{
protected/strains/strain-form protected/strains/strain-form
strain=strain strain=model
species=species speciesList=speciesList
canAdd=metaData.canAdd add-characteristic=(action "addCharacteristic")
save="save" allCharacteristics=allCharacteristics
cancel="cancel" save-measurement=(action "saveMeasurement")
delete-measurement=(action "deleteMeasurement")
on-save=(action "save")
on-cancel=(action "cancel")
}} }}

View file

@ -1,6 +0,0 @@
import Ember from 'ember';
export default Ember.Controller.extend({
sortParams: ['sortOrder'],
sortedStrains: Ember.computed.sort('model', 'sortParams'),
});

View file

@ -1,17 +1,10 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Route.extend({ const { Route } = Ember;
currentUser: Ember.inject.service('session-account'),
export default Route.extend({
model: function() { model: function() {
return this.store.findAll('strain'); return this.store.findAll('strain');
}, },
setupController: function(controller, model) {
controller.set('model', model);
this.get('currentUser.account').then((user) => {
controller.set('metaData', user.get('metaData'));
});
},
}); });

View file

@ -0,0 +1,12 @@
import Ember from 'ember';
import SetupMetaData from '../../../../../mixins/setup-metadata';
const { Component, computed: { sort } } = Ember;
export default Component.extend(SetupMetaData, {
strains: null,
sortParams: ['fullName'],
sortedStrains: sort('strains', 'sortParams'),
});

View file

@ -0,0 +1,26 @@
<h3 id="total-strains">Total strains: {{strains.length}}</h3>
{{add-button label="Add Strain" link="protected.strains.new" canAdd=metaData.canAdd}}
<table class="flakes-table">
<thead>
<tr>
<th>Species</th>
<th>Total Measurements</th>
</tr>
</thead>
<tbody>
{{#each sortedStrains as |strain|}}
<tr>
<td>
{{#link-to 'protected.strains.show' strain classBinding="data.typeStrain:type-strain"}}
{{strain.fullNameMU}}
{{/link-to}}
</td>
<td>
{{strain.totalMeasurements}}
</td>
</tr>
{{/each}}
</tbody>
</table>

View file

@ -1,27 +1,6 @@
<h2>{{genus-name}} Strains</h2> <h2>{{genus-name}} Strains</h2>
<h3>Total strains: {{model.length}}</h3>
{{add-button label="Add Strain" link="protected.strains.new" canAdd=metaData.canAdd}} {{
protected/strains/index/strain-table
<table class="flakes-table"> strains=model
<thead> }}
<tr>
<th>Species</th>
<th>Total Measurements</th>
</tr>
</thead>
<tbody>
{{#each sortedStrains as |row|}}
<tr>
<td>
{{#link-to 'protected.strains.show' row classBinding="data.typeStrain:type-strain"}}
{{row.fullNameMU}}
{{/link-to}}
</td>
<td>
{{row.totalMeasurements}}
</td>
</tr>
{{/each}}
</tbody>
</table>

View file

@ -0,0 +1,69 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
tagName: 'tr',
// Read-only attributes
isEditing: false,
allCharacteristics: null,
measurement: null,
isDirty: null,
// Actions
"save-measurement": null,
"delete-measurement": null,
// Property mapping
propertiesList: ['characteristic', 'value', 'notes'],
characteristic: null,
value: null,
notes: null,
resetOnInit: Ember.on('init', function() {
this.get('propertiesList').forEach((field) => {
const valueInMeasurement = this.get('measurement').get(field);
this.set(field, valueInMeasurement);
});
}),
updateField: function(property, value) {
this.set(property, value);
// Manually compare against passed in value
if (this.get('measurement').get(property) !== value) {
this.set('isDirty', true);
} else {
this.set('isDirty', false);
}
},
actions: {
edit: function() {
this.toggleProperty('isEditing');
},
save: function() {
this.attrs['save-measurement'](this.get('measurement'), this.getProperties(this.get('propertiesList')));
this.toggleProperty('isEditing');
},
delete: function() {
this.attrs['delete-measurement'](this.get('measurement'));
},
characteristicDidChange: function(value) {
const newCharacteristic = this.get('allCharacteristics').findBy('id', value);
this.updateField('characteristic', newCharacteristic);
},
valueDidChange: function(value) {
this.updateField('value', value);
},
notesDidChange: function(value) {
this.updateField('notes', value);
},
},
});

View file

@ -0,0 +1,52 @@
{{#if isEditing}}
<td></td>
<td>
<select onchange={{action "characteristicDidChange" value="target.value"}}>
{{#each allCharacteristics as |characteristicChoice|}}
<option value={{characteristicChoice.id}} selected={{equal characteristic.id characteristicChoice.id}}>{{characteristicChoice.characteristicName}}</option>
{{/each}}
</select>
</td>
<td>
{{one-way-input type="text" class="measurement-value" value=value update=(action "valueDidChange")}}
</td>
<td>
{{one-way-input type="text" class="measurement-notes" value=notes update=(action "notesDidChange")}}
</td>
{{#if canEdit}}
<td>
{{#if isDirty}}
<button class="button-green smaller" {{action 'save'}}>
Save
</button>
{{else}}
<button class="button-gray smaller" {{action 'save'}}>
Cancel
</button>
{{/if}}
</td>
{{/if}}
{{else}}
<td>
{{{measurement.characteristic.characteristicTypeName}}}
</td>
<td>
{{#link-to 'protected.characteristics.show' measurement.characteristic.id}}
{{{measurement.characteristic.characteristicName}}}
{{/link-to}}
</td>
<td>
{{measurement.value}}
</td>
<td>
{{measurement.notes}}
</td>
{{#if canEdit}}
<td>
<button class="button-gray smaller" {{action 'edit'}}>
Edit
</button>
{{delete-button delete=(action 'delete')}}
</td>
{{/if}}
{{/if}}

View file

@ -0,0 +1,55 @@
import Ember from 'ember';
const { Component, computed } = Ember;
const { sort } = computed;
export default Component.extend({
// Passed in
strain: null,
allCharacteristics: null,
canEdit: false,
canAdd: false,
// Actions
"add-characteristic": null,
"save-measurement": null,
"delete-measurement": null,
// Properties
sortParams: ['characteristic.characteristicTypeName', 'characteristic.sortOrder', 'characteristic.characteristicName'],
sortAsc: true,
paramsChanged: false,
sortedMeasurements: sort('strain.measurements', 'sortParams'),
measurementsPresent: computed('strain.measurements', function() {
return this.get('strain.measurements.length') > 0;
}),
actions: {
addCharacteristic: function() {
const newChar = this.attrs['add-characteristic']();
this.get('strain.measurements').addObject(newChar);
},
changeSortParam: function(col) {
const sort = this.get('sortAsc') ? 'asc' : 'desc';
this.set('sortParams', [`${col}:${sort}`]);
this.set('paramsChanged', true);
this.toggleProperty('sortAsc');
},
resetSortParam: function() {
this.set('sortParams', ['characteristic.characteristicTypeName', 'characteristic.sortOrder', 'characteristic.characteristicName']);
this.set('paramsChanged', false);
this.set('sortAsc', true);
},
saveMeasurement: function(measurement, properties) {
return this.attrs['save-measurement'](measurement, properties);
},
deleteMeasurement: function(measurement) {
return this.attrs['delete-measurement'](measurement);
},
},
});

View file

@ -1,17 +1,17 @@
{{#if canAdd}} {{#if canAdd}}
<br> <br>
<button class="button-green smaller" {{action "addCharacteristic"}}> <button class="button-green smaller" {{action "addCharacteristic"}}>
Add characteristic Add characteristic
</button> </button>
<br><br> <br><br>
{{/if}} {{/if}}
{{#if measurementsPresent}} {{#if measurementsPresent}}
{{#if paramsChanged}} {{#if paramsChanged}}
<button class="button-gray smaller" {{action 'resetSortParam'}}> <button class="button-gray smaller" {{action 'resetSortParam'}}>
Reset sort Reset sort
</button> </button>
{{/if}} {{/if}}
<table class="flakes-table"> <table class="flakes-table">
<colgroup> <colgroup>
{{#if canEdit}} {{#if canEdit}}
@ -41,8 +41,11 @@
<tbody> <tbody>
{{#each sortedMeasurements as |measurement|}} {{#each sortedMeasurements as |measurement|}}
{{ {{
protected/strains/show/measurements-table-row protected/strains/measurements-table-row
row=measurement measurement=measurement
save-measurement=(action "saveMeasurement")
delete-measurement=(action "deleteMeasurement")
allCharacteristics=allCharacteristics
canEdit=canEdit canEdit=canEdit
}} }}
{{/each}} {{/each}}

View file

@ -1,29 +1,10 @@
import Ember from 'ember'; import Ember from 'ember';
import ajaxError from '../../../../utils/ajax-error'; import SaveModel from '../../../../mixins/save-model';
export default Ember.Controller.extend({ const { Controller } = Ember;
actions: {
save: function() {
let strain = this.get('strain');
if (strain.get('hasDirtyAttributes')) { export default Controller.extend(SaveModel, {
strain.save().then((strain) => { // Required for SaveModel mixin
this.transitionToRoute('protected.strains.show', strain); fallbackRouteSave: 'protected.strains.show',
}, () => { fallbackRouteCancel: 'protected.strains.index',
ajaxError(strain.get('errors'), this.get('flashMessages'));
});
} else {
strain.destroyRecord().then(() => {
this.transitionToRoute('protected.strains.index');
});
}
},
cancel: function() {
this.get('strain').destroyRecord().then(() => {
this.transitionToRoute('protected.strains.index');
});
},
},
}); });

View file

@ -1,16 +1,12 @@
import Ember from 'ember'; import Ember from 'ember';
import ElevatedAccess from '../../../../mixins/elevated-access';
export default Ember.Route.extend({ const { Route } = Ember;
currentUser: Ember.inject.service('session-account'),
beforeModel: function(transition) { export default Route.extend(ElevatedAccess, {
this._super(transition); // Required for ElevatedAccess mixin
this.get('currentUser.account').then((user) => { fallbackRouteBefore: 'protected.strains.index',
if (user.get('isReader')) { fallbackRouteAfter: 'protected.strains.show',
this.transitionTo('protected.strains.index');
}
});
},
model: function() { model: function() {
return Ember.RSVP.hash({ return Ember.RSVP.hash({
@ -19,19 +15,28 @@ export default Ember.Route.extend({
}); });
}, },
// Overriding afterModel because of RSVP hash
afterModel: function(models) {
if (!models.strain.get('isNew') && !models.strain.get('canEdit')) {
this.transitionTo(this.get('fallbackRouteAfter'), models.strain.get('id'));
}
},
// Setting up controller because of RSVP hash
setupController: function(controller, models) { setupController: function(controller, models) {
controller.setProperties(models); controller.set('model', models.strain);
controller.set('speciesList', models.species);
}, },
actions: { actions: {
// Overriding willTransition because of RSVP hash
willTransition: function(/*transition*/) { willTransition: function(/*transition*/) {
const controller = this.get('controller'); const controller = this.get('controller');
const strain = controller.get('strain'); const model = controller.get('model');
if (strain.get('isNew')) { if (model.get('isNew')) {
strain.destroyRecord(); model.destroyRecord();
} }
}, },
}, },
}); });

View file

@ -1,7 +1,7 @@
{{ {{
protected/strains/strain-form protected/strains/strain-form
strain=strain strain=model
species=species speciesList=speciesList
save="save" on-save=(action "save")
cancel="cancel" on-cancel=(action "cancel")
}} }}

View file

@ -1,12 +1,9 @@
import Ember from 'ember'; import Ember from 'ember';
import DeleteModel from '../../../../mixins/delete-model';
export default Ember.Controller.extend({ const { Controller } = Ember;
actions: {
delete: function() {
this.get('model').destroyRecord().then(() => {
this.transitionToRoute('protected.strains.index');
});
},
},
export default Controller.extend(DeleteModel, {
// Required for DeleteModel mixin
transitionRoute: 'protected.strains.index',
}); });

View file

@ -1,47 +0,0 @@
import Ember from 'ember';
import ajaxError from '../../../../../utils/ajax-error';
export default Ember.Component.extend({
tagName: 'tr',
isEditing: false,
oldCharacteristicId: function() {
let json = this.get('row').toJSON();
return json.characteristic;
}.property(),
rowChanged: Ember.computed('row.notes', 'row.value', 'row.characteristic.id', function() {
return this.get('row.hasDirtyAttributes') ||
this.get('oldCharacteristicId') !== this.get('row.characteristic.id');
}),
actions: {
edit: function() {
// The parent table fetches all of the characteristics ahead of time
this.set('characteristics', this.store.peekAll('characteristic'));
this.toggleProperty('isEditing');
},
save: function() {
if (this.get('rowChanged')) {
this.get('row').save().then(() => {
this.get('flashMessages').clearMessages();
this.toggleProperty('isEditing');
}, () => {
ajaxError(this.get('row.errors'), this.get('flashMessages'));
});
} else {
this.toggleProperty('isEditing');
}
},
delete: function() {
let char = this.get('row.characteristic');
if (char.get('isNew')) {
char.destroyRecord();
}
this.get('row').destroyRecord();
}
},
});

View file

@ -1,54 +0,0 @@
{{#if isEditing}}
<td></td>
<td>
{{
select-2
multiple=false
content=characteristics
value=row.characteristic
optionLabelPath="characteristicName"
}}
</td>
<td>
{{input value=row.value}}
</td>
<td>
{{input value=row.notes}}
</td>
{{#if canEdit}}
<td>
{{#if rowChanged}}
<button class="button-green smaller" {{action 'save'}}>
Save
</button>
{{else}}
<button class="button-gray smaller" {{action 'save'}}>
Cancel
</button>
{{/if}}
</td>
{{/if}}
{{else}}
<td>
{{{row.characteristic.characteristicTypeName}}}
</td>
<td>
{{#link-to 'protected.characteristics.show' row.characteristic.id}}
{{{row.characteristic.characteristicName}}}
{{/link-to}}
</td>
<td>
{{row.value}}
</td>
<td>
{{row.notes}}
</td>
{{#if canEdit}}
<td>
<button class="button-gray smaller" {{action 'edit'}}>
Edit
</button>
{{delete-button delete=(action 'delete')}}
</td>
{{/if}}
{{/if}}

View file

@ -1,47 +0,0 @@
import Ember from 'ember';
export default Ember.Component.extend({
measurementsPresent: function() {
return this.get('model.measurements.length') > 0;
}.property('model.measurements'),
fetchCharacteristics: function() {
if (this.get('canEdit')) {
this.store.findAll('characteristic');
}
}.on('didInsertElement'),
sortParams: ['characteristic.characteristicTypeName', 'characteristic.sortOrder', 'characteristic.characteristicName'],
sortAsc: true,
paramsChanged: false,
sortedMeasurements: Ember.computed.sort('model.measurements', 'sortParams'),
actions: {
addCharacteristic: function() {
const c = this.store.createRecord('characteristic', {
sortOrder: -999
});
const m = this.store.createRecord('measurement', {
characteristic: c
});
this.get('model.measurements').addObject(m);
},
changeSortParam: function(col) {
let sort = this.get('sortAsc') ? 'asc' : 'desc';
let sortCol = `${col}:${sort}`;
this.set('sortParams', [sortCol]);
this.set('paramsChanged', true);
this.toggleProperty('sortAsc');
return false;
},
resetSortParam: function() {
this.set('sortParams', ['characteristic.characteristicTypeName', 'characteristic.sortOrder', 'characteristic.characteristicName']);
this.set('paramsChanged', false);
this.set('sortAsc', true);
return false;
},
},
});

View file

@ -1,8 +1,10 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Route.extend({ const { Route } = Ember;
export default Route.extend({
model: function(params) { model: function(params) {
return this.store.findRecord('strain', params.strain_id, { reload: true }); return this.store.findRecord('strain', params.strain_id);
}, },
}); });

View file

@ -0,0 +1,14 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
strain: null,
"on-delete": null,
actions: {
deleteStrain: function() {
return this.attrs['on-delete']();
},
},
});

View file

@ -0,0 +1,105 @@
<div class="span-1">
<fieldset class="flakes-information-box">
<legend>
{{strain.strainNameMU}}
</legend>
{{! ROW 1 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Species</dt>
<dd>
{{#link-to 'protected.species.show' strain.species.id}}
<em>{{strain.species.speciesNameMU}}</em>
{{/link-to}}
</dd>
</dl>
<dl class="span-1">
<dt>Type Strain?</dt>
<dd>
{{if strain.typeStrain 'Yes' 'No'}}
</dd>
</dl>
</div>
{{! ROW 2 }}
<div class="grid-3 gutter-20">
<dl class="span-1">
<dt>Accession Numbers</dt>
<dd>
{{strain.accessionNumbers}}
</dd>
</dl>
<dl class="span-1">
<dt>Genbank</dt>
<dd>
{{genbank-url genbank=strain.genbank}}
</dd>
</dl>
<dl class="span-1">
<dt>Whole Genome Sequence</dt>
<dd>
{{genbank-url genbank=strain.wholeGenomeSequence}}
</dd>
</dl>
</div>
{{! ROW 3 }}
<div class="grid-1 gutter-20">
<dl class="span-1">
<dt>Isolated From</dt>
<dd>
{{{strain.isolatedFrom}}}
</dd>
</dl>
</div>
{{! ROW 4 }}
<div class="grid-1 gutter-20">
<dl class="span-1">
<dt>Notes</dt>
<dd>
{{#if strain.notes}}
{{{strain.notes}}}
{{else}}
No notes.
{{/if}}
</dd>
</dl>
</div>
{{! ROW 5 }}
<div class="grid-1 gutter-20">
<dl class="span-1">
<dt>Characteristics</dt>
<dd>
{{
protected/strains/measurements-table
strain=strain
canEdit=false
canAdd=false
}}
</dd>
</dl>
</div>
{{! ROW 6 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Record Created</dt>
<dd>{{null-time strain.createdAt 'LL'}}</dd>
</dl>
<dl class="span-1">
<dt>Record Updated</dt>
<dd>{{null-time strain.updatedAt 'LL'}}</dd>
</dl>
</div>
</fieldset>
</div>
{{#if strain.canEdit}}
<br>
{{#link-to 'protected.strains.edit' strain.id class="button-gray smaller"}}
Edit
{{/link-to}}
{{delete-button delete=(action 'deleteStrain')}}
{{/if}}

View file

@ -1,105 +1,5 @@
<div class="span-1"> {{
<fieldset class="flakes-information-box"> protected/strains/show/strain-card
<legend> strain=model
Strain {{model.strainNameMU}} on-delete=(action 'delete')
</legend> }}
{{! ROW 1 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Species</dt>
<dd>
{{#link-to 'protected.species.show' model.species.id}}
<em>{{model.species.speciesNameMU}}</em>
{{/link-to}}
</dd>
</dl>
<dl class="span-1">
<dt>Type Strain?</dt>
<dd>
{{if model.typeStrain 'Yes' 'No'}}
</dd>
</dl>
</div>
{{! ROW 2 }}
<div class="grid-3 gutter-20">
<dl class="span-1">
<dt>Accession Numbers</dt>
<dd>
{{model.accessionNumbers}}
</dd>
</dl>
<dl class="span-1">
<dt>Genbank</dt>
<dd>
{{genbank-url genbank=model.genbank}}
</dd>
</dl>
<dl class="span-1">
<dt>Whole Genome Sequence</dt>
<dd>
{{genbank-url genbank=model.wholeGenomeSequence}}
</dd>
</dl>
</div>
{{! ROW 3 }}
<div class="grid-1 gutter-20">
<dl class="span-1">
<dt>Isolated From</dt>
<dd>
{{{model.isolatedFrom}}}
</dd>
</dl>
</div>
{{! ROW 4 }}
<div class="grid-1 gutter-20">
<dl class="span-1">
<dt>Notes</dt>
<dd>
{{#if model.notes}}
{{{model.notes}}}
{{else}}
No notes.
{{/if}}
</dd>
</dl>
</div>
{{! ROW 5 }}
<div class="grid-1 gutter-20">
<dl class="span-1">
<dt>Characteristics</dt>
<dd>
{{
protected/strains/show/measurements-table
model=model
canEdit=false
canAdd=false
}}
</dd>
</dl>
</div>
{{! ROW 6 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Record Created</dt>
<dd>{{null-time model.createdAt 'LL'}}</dd>
</dl>
<dl class="span-1">
<dt>Record Updated</dt>
<dd>{{null-time model.updatedAt 'LL'}}</dd>
</dl>
</div>
</fieldset>
</div>
{{#if model.canEdit}}
<br>
{{#link-to 'protected.strains.edit' model.id class="button-gray smaller"}}
Edit
{{/link-to}}
{{delete-button delete=(action 'delete')}}
{{/if}}

View file

@ -1,21 +1,106 @@
import Ember from 'ember'; import Ember from 'ember';
import SetupMetaData from '../../../../mixins/setup-metadata';
const { Component } = Ember;
export default Component.extend(SetupMetaData, {
// Read-only attributes
strain: null,
isNew: null,
isDirty: false,
speciesList: null,
allCharacteristics: null,
// Actions
"on-save": null,
"on-cancel": null,
"on-update": null,
"add-characteristic": null,
"save-measurement": null,
"delete-measurement": null,
// Property mapping
propertiesList: ['strainName', 'typeStrain', 'species', 'isolatedFrom', 'accessionNumbers', 'genbank', 'wholeGenomeSequence', 'notes'],
strainName: null,
typeStrain: null,
species: null,
isolatedFrom: null,
accessionNumbers: null,
genbank: null,
wholeGenomeSequence: null,
notes: null,
resetOnInit: Ember.on('init', function() {
this.get('propertiesList').forEach((field) => {
const valueInStrain = this.get('strain').get(field);
this.set(field, valueInStrain);
});
// Read-only attributes
this.set('isNew', this.get('strain.isNew'));
}),
updateField: function(property, value) {
this.set(property, value);
// Manually compare against passed in value
if (this.get('strain').get(property) !== value) {
this.set('isDirty', true);
} else {
this.set('isDirty', false);
}
},
export default Ember.Component.extend({
actions: { actions: {
save: function() { save: function() {
this.sendAction('save'); return this.attrs['on-save'](this.getProperties(this.get('propertiesList')));
}, },
cancel: function() { cancel: function() {
this.sendAction('cancel'); return this.attrs['on-cancel']();
},
addCharacteristic: function() {
return this.attrs['add-characteristic']();
},
saveMeasurement: function(measurement, properties) {
return this.attrs['save-measurement'](measurement, properties);
},
deleteMeasurement: function(measurement) {
return this.attrs['delete-measurement'](measurement);
},
strainNameDidChange: function(value) {
this.updateField('strainName', value);
},
typeStrainDidChange: function() {
this.updateField('typeStrain', !this.get('typeStrain'));
},
speciesDidChange: function(value) {
const newSpecies = this.get('speciesList').findBy('id', value);
this.updateField('species', newSpecies);
}, },
isolatedFromDidChange: function(value) { isolatedFromDidChange: function(value) {
this.set('strain.isolatedFrom', value); this.updateField('isolatedFrom', value);
},
accessionNumbersNameDidChange: function(value) {
this.updateField('accessionNumbers', value);
},
genbankDidChange: function(value) {
this.updateField('genbank', value);
},
wholeGenomeSequenceDidChange: function(value) {
this.updateField('wholeGenomeSequence', value);
}, },
notesDidChange: function(value) { notesDidChange: function(value) {
this.set('strain.notes', value); this.updateField('strain.notes', value);
}, },
}, },
}); });

View file

@ -1,68 +1,72 @@
<form class="grid-form" {{action 'save' on='submit'}}> <form class="grid-form" {{action 'save' on='submit'}}>
<fieldset> <fieldset>
<legend><em>{{strain.strainName}}</em></legend> <legend><em>{{strainName}}</em></legend>
<div data-row-span="2"> <div data-row-span="2">
<div data-field-span="1"> <div data-field-span="1">
<label>Strain Name</label> <label>Strain Name</label>
{{input value=strain.strainName}} {{one-way-input type="text" class="strain-name" value=strainName update=(action "strainNameDidChange")}}
</div> </div>
<div data-field-span="1"> <div data-field-span="1">
<label>Type Strain?</label> <label>Type Strain?</label>
{{input type="checkbox" checked=strain.typeStrain}} {{if strain.typeStrain 'Yes' 'No'}} <input type="checkbox" checked={{typeStrain}} value="{{typeStrain}}" onchange={{action "typeStrainDidChange"}}>
{{if typeStrain 'Yes' 'No'}}
</div> </div>
</div> </div>
<div data-row-span="2"> <div data-row-span="2">
<div data-field-span="2"> <div data-field-span="2">
<label>Species</label> <label>Species</label>
{{ <select onchange={{action "speciesDidChange" value="target.value"}}>
select-2 {{#each speciesList as |speciesChoice|}}
content=species <option value={{speciesChoice.id}} selected={{equal species.id speciesChoice.id}}>{{speciesChoice.speciesName}}</option>
optionLabelPath="speciesName" {{/each}}
value=strain.species </select>
}}
</div> </div>
</div> </div>
<div data-row-span="2"> <div data-row-span="2">
<div data-field-span="2"> <div data-field-span="2">
<label>Isolated From</label> <label>Isolated From</label>
{{text-editor value=strain.isolatedFrom update=(action "isolatedFromDidChange")}} {{text-editor value=isolatedFrom update=(action "isolatedFromDidChange")}}
</div> </div>
</div> </div>
<div data-row-span="3"> <div data-row-span="3">
<div data-field-span="1"> <div data-field-span="1">
<label>Accession Numbers</label> <label>Accession Numbers</label>
{{input value=strain.accessionNumbers}} {{one-way-input type="text" class="accession-numbers" value=accessionNumbers update=(action "accessionNumbersNameDidChange")}}
</div> </div>
<div data-field-span="1"> <div data-field-span="1">
<label>GenBank</label> <label>GenBank</label>
{{input value=strain.genbank}} {{one-way-input type="text" class="genbank" value=genbank update=(action "genbankDidChange")}}
</div> </div>
<div data-field-span="1"> <div data-field-span="1">
<label>Whole Genome Sequence</label> <label>Whole Genome Sequence</label>
{{input value=strain.wholeGenomeSequence}} {{one-way-input type="text" class="whole-genome-sequenc" value=wholeGenomeSequence update=(action "wholeGenomeSequenceDidChange")}}
</div> </div>
</div> </div>
<div data-row-span="2"> <div data-row-span="2">
<div data-field-span="2"> <div data-field-span="2">
<label>Notes</label> <label>Notes</label>
{{text-editor value=strain.notes update=(action "notesDidChange")}} {{text-editor value=notes update=(action "notesDidChange")}}
</div> </div>
</div> </div>
</fieldset> </fieldset>
<div> <div>
{{ {{
protected/strains/show/measurements-table protected/strains/measurements-table
model=strain strain=strain
add-characteristic=(action "addCharacteristic")
allCharacteristics=allCharacteristics
save-measurement=(action "saveMeasurement")
delete-measurement=(action "deleteMeasurement")
canEdit=strain.canEdit canEdit=strain.canEdit
canAdd=canAdd canAdd=metaData.canAdd
}} }}
</div> </div>
<br> <br>
<a class="button-red smaller" {{action 'cancel'}}> <a class="button-red smaller" {{action 'cancel'}}>
Cancel Cancel
</a> </a>
{{#if strain.hasDirtyAttributes}} {{#if isDirty}}
<button type="submit" class="button-green smaller"> <button type="submit" class="button-green smaller save-strain">
Save Save
</button> </button>
{{/if}} {{/if}}

View file

@ -0,0 +1,76 @@
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from '../helpers/start-app';
import { authenticateSession } from '../helpers/ember-simple-auth';
module('Acceptance | strains', {
beforeEach: function() {
this.application = startApp();
authenticateSession(this.application, {
access_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiYWN0ZGIiLCJzdWIiOiIxIiwiZXhwIjoxNDQ2NTAyMjI2LCJpYXQiOjE0NDY0OTg2MjZ9.vIjKHAsp2TkCV505EbtCo2xQT-2oQkB-Nv5y0b6E7Mg"
});
server.create('users', { role: 'A', canEdit: true });
},
afterEach: function() {
Ember.run(this.application, 'destroy');
}
});
test('visiting /strains', function(assert) {
const species = server.create('species');
const strains = server.createList('strains', 20, { species: species.id });
visit('/strains');
andThen(function() {
assert.equal(currentURL(), '/strains');
assert.equal(find(".flakes-table > tbody > tr").length, strains.length);
assert.equal(find("#total-strains").text(), "Total strains: 20");
});
});
test('visiting /strains/:id', function(assert) {
const species = server.create('species');
const strain = server.create('strains', { species: species.id });
visit(`/strains/${strain.id}`);
andThen(function() {
assert.equal(currentURL(), `/strains/${strain.id}`);
const typeStrain = strain.typeStrain ? 'T' : '';
assert.equal(find(".flakes-information-box > legend").text().trim(), `${strain.strainName}${typeStrain}`);
});
});
test('editing /strains/:id/edit', function(assert) {
const species = server.create('species');
const strain = server.create('strains', { canEdit: true , species: species.id });
visit(`/strains/${strain.id}/edit`);
andThen(function() {
assert.equal(currentURL(), `/strains/${strain.id}/edit`);
fillIn('.strain-name', 'Revised Strain Name');
click('.save-strain');
andThen(function() {
assert.equal(currentURL(), `/strains/${strain.id}`);
const typeStrain = strain.typeStrain ? 'T' : '';
assert.equal(find(".flakes-information-box > legend").text().trim(), `Revised Strain Name${typeStrain}`);
});
});
});
test('creating /strains/new', function(assert) {
visit(`/strains/new`);
andThen(function() {
assert.equal(currentURL(), `/strains/new`);
fillIn('.strain-name', 'New Strain Name');
click('.save-strain');
andThen(function() {
assert.equal(find(".flakes-information-box > legend").text().trim(), `New Strain Name`);
assert.equal(server.db.strains.length, 1);
});
});
});