fixed merge conflict in tests/index

This commit is contained in:
Matthew Dillon 2015-11-13 08:33:04 -07:00
commit 8730b6659f
116 changed files with 1793 additions and 1106 deletions

View file

@ -1,3 +1,5 @@
import Ember from 'ember';
export default Ember.Route.extend({});
const { Route } = Ember;
export default Route.extend({});

View file

@ -6,5 +6,5 @@ const { Controller } = Ember;
export default Controller.extend(SaveModel, {
// Required for SaveModel mixin
fallbackRouteSave: 'protected.characteristics.show',
fallbackRouteCancel: 'protected.characteristics.index',
fallbackRouteCancel: 'protected.characteristics.show',
});

View file

@ -1,41 +1,22 @@
import Ember from 'ember';
export default Ember.Controller.extend({
const { Controller } = Ember;
export default Controller.extend({
selectedStrains: null,
selectedCharacteristics: null,
actions: {
search: function() {
let query = {
strain_ids: this.get('selectedStrains'),
characteristic_ids: this.get('selectedCharacteristics'),
};
this.transitionToRoute('protected.compare.results', {queryParams: query});
search: function(query) {
this.transitionToRoute('protected.compare.results', { queryParams: query });
},
selectAllStrains: function() {
let strains = this.get('strains');
let strain_ids = [];
strains.forEach((strain) => {
strain_ids.push(strain.id);
});
this.set('selectedStrains', strain_ids.join(","));
updateStrainSelection: function(selection) {
this.set('selectedStrains', selection);
},
deselectAllStrains: function() {
this.set('selectedStrains', '');
updateCharacteristicSelection: function(selection) {
this.set('selectedCharacteristics', selection);
},
selectAllCharacteristics: function() {
let chars = this.get('characteristics');
let char_ids = [];
chars.forEach((char) => {
char_ids.push(char.id);
});
this.set('selectedCharacteristics', char_ids.join(","));
},
deselectAllCharacteristics: function() {
this.set('selectedCharacteristics', '');
},
}
});

View file

@ -1,31 +1,8 @@
import Ember from 'ember';
export default Ember.Controller.extend({
const { Controller } = Ember;
export default Controller.extend({
queryParams: ['strain_ids', 'characteristic_ids'],
csvLink: function() {
let token = encodeURIComponent(this.get('session.secure.token'));
return `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/` +
`compare?token=${token}&strain_ids=${this.get('strain_ids')}&` +
`characteristic_ids=${this.get('characteristic_ids')}&mimeType=csv`;
}.property('strain_ids', 'characteristic_ids').readOnly(),
strains: function() {
let strains = [];
let strain_ids = this.get('strain_ids').split(',');
strain_ids.forEach((id) => {
strains.push(this.store.peekRecord('strain', id));
});
return strains;
}.property('strain_ids'),
characteristics: function() {
let characteristics = [];
let characteristic_ids = this.get('characteristic_ids').split(',');
characteristic_ids.forEach((id) => {
characteristics.push(this.store.peekRecord('characteristic', id));
});
return characteristics;
}.property('characteristic_ids'),
});

View file

@ -0,0 +1,19 @@
import Ember from 'ember';
const { Component, computed, inject: { service } } = Ember;
export default Component.extend({
session: service(),
strains: null,
characteristics: null,
strain_ids: null,
characteristic_ids: null,
csvLink: computed('strain_ids', 'characteristic_ids', function() {
const token = encodeURIComponent(this.get('session.data.authenticated.access_token'));
return `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/` +
`compare?token=${token}&strain_ids=${this.get('strain_ids')}&` +
`characteristic_ids=${this.get('characteristic_ids')}&mimeType=csv`;
}),
});

View file

@ -0,0 +1,25 @@
<table class="flakes-table">
<thead>
<tr>
<th>Characteristic</th>
{{#each strains as |strain|}}
<th>
{{#link-to 'protected.strains.show' strain.id classBinding="data.typeStrain:type-strain"}}
{{strain.fullNameMU}}
{{/link-to}}
</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each characteristics as |row|}}
<tr>
{{#each row key="@index" as |col|}}
<td>{{col}}</td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
<hr>
<a href="{{csvLink}}" download>Download as CSV</a>

View file

@ -1,8 +1,10 @@
import Ember from 'ember';
import ajaxRequest from '../../../../utils/ajax-request';
export default Ember.Route.extend({
session: Ember.inject.service('session'),
const { Route, $: { isEmptyObject }, inject: { service } } = Ember;
export default Route.extend({
session: service(),
ajax: service(),
queryParams: {
strain_ids: {
@ -15,8 +17,9 @@ export default Ember.Route.extend({
beforeModel: function(transition) {
this._super(transition);
if (Ember.$.isEmptyObject(transition.queryParams.strain_ids) ||
Ember.$.isEmptyObject(transition.queryParams.characteristic_ids)) {
const strain_ids = transition.queryParams.strain_ids;
const characteristic_ids = transition.queryParams.characteristic_ids;
if (isEmptyObject(strain_ids) || isEmptyObject(characteristic_ids)) {
this.transitionTo('protected.compare');
}
},
@ -26,23 +29,35 @@ export default Ember.Route.extend({
this.transitionTo('protected.compare');
}
let compare = this.controllerFor('protected.compare');
const compare = this.controllerFor('protected.compare');
compare.set('selectedStrains', params.strain_ids);
compare.set('selectedCharacteristics', params.characteristic_ids);
let url = `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/compare`;
let options = {
method: 'GET',
data: params,
};
return ajaxRequest(url, options, this.get('session'));
return this.get('ajax').request('/compare', { data: params });
},
setupController: function(controller, model) {
model.forEach((m, i) => {
let c = this.store.peekRecord('characteristic', m[0]);
const c = this.store.peekRecord('characteristic', m[0]);
model[i][0] = c.get('characteristicName');
});
const compare = this.controllerFor('protected.compare');
const strains = [];
const strain_ids = compare.get('selectedStrains').split(',');
strain_ids.forEach((id) => {
strains.push(this.store.peekRecord('strain', id));
});
controller.set('strains', strains);
const characteristics = [];
const characteristic_ids = compare.get('selectedCharacteristics').split(',');
characteristic_ids.forEach((id) => {
characteristics.push(this.store.peekRecord('characteristic', id));
});
controller.set('characteristics', characteristics);
controller.set('model', model);
},

View file

@ -1,25 +1,7 @@
<table class="flakes-table">
<thead>
<tr>
<th>Characteristic</th>
{{#each strains as |strain|}}
<th>
{{#link-to 'protected.strains.show' strain.id classBinding="data.typeStrain:type-strain"}}
{{strain.fullNameMU}}
{{/link-to}}
</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each model as |row|}}
<tr>
{{#each row key="@index" as |col|}}
<td>{{col}}</td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
<hr>
<a href="{{csvLink}}" download>Download as CSV</a>
{{
protected/compare/results/results-table
strains=strains
characteristics=model
strain_ids=strain_ids
characteristic_ids=characteristic_ids
}}

View file

@ -1,6 +1,8 @@
import Ember from 'ember';
export default Ember.Route.extend({
const { Route } = Ember;
export default Route.extend({
model: function() {
return this.store.findAll('characteristic');
},

View file

@ -0,0 +1,61 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
characteristics: null,
strains: null,
"on-search": null,
"update-strains": null,
"update-characteristics": null,
selectedStrains: null,
selectedCharacteristics: null,
updateStrains: function(selection) {
this.set('selectedStrains', selection);
this.attrs['update-strains'](this.get('selectedStrains'));
},
updateCharacteristics: function(selection) {
this.set('selectedCharacteristics', selection);
this.attrs['update-characteristics'](this.get('selectedCharacteristics'));
},
actions: {
search: function() {
const query = {
strain_ids: this.get('selectedStrains'),
characteristic_ids: this.get('selectedCharacteristics'),
};
this.attrs['on-search'](query);
},
selectAllStrains: function() {
const strains = this.get('strains');
const strain_ids = [];
strains.forEach((strain) => {
strain_ids.push(strain.get('id'));
});
this.updateStrains(strain_ids.join(","));
},
deselectAllStrains: function() {
this.updateStrains("");
},
selectAllCharacteristics: function() {
const chars = this.get('characteristics');
const char_ids = [];
chars.forEach((char) => {
char_ids.push(char.get('id'));
});
this.updateCharacteristics(char_ids.join(","));
},
deselectAllCharacteristics: function() {
this.updateCharacteristics("");
},
},
});

View file

@ -0,0 +1,53 @@
<div class="span-1">
<fieldset>
<form {{action 'search' on='submit'}}>
<ul>
<li>
<label>Strains</label>
{{
select-2
multiple=true
content=strains
value=selectedStrains
optionValuePath="id"
optionLabelPath="fullNameMU"
placeholder="Select one or more strains"
}}
</li>
<li>
<button class="action button-green smaller right" {{action 'selectAllStrains'}}>
Select All
</button>
<button class="action button-red smaller right" {{action 'deselectAllStrains'}}>
Deselect All
</button>
</li>
<li>
<label>Characteristics</label>
{{
select-2
multiple=true
content=characteristics
value=selectedCharacteristics
optionValuePath="id"
optionLabelPath="characteristicName"
placeholder="Select one or more characteristics"
}}
</li>
<li>
<button class="action button-green smaller right" {{action 'selectAllCharacteristics'}}>
Select All
</button>
<button class="action button-red smaller right" {{action 'deselectAllCharacteristics'}}>
Deselect All
</button>
</li>
<li>
<button type="submit" class="action button-gray smaller right">
Search
</button>
</li>
</ul>
</form>
</fieldset>
</div>

View file

@ -1,57 +1,14 @@
<h2>{{genus-name}} - Compare Strains</h2>
<div class="span-1">
<fieldset>
<form {{action 'search' on='submit'}}>
<ul>
<li>
<label>Strains</label>
{{
select-2
multiple=true
content=strains
value=selectedStrains
optionValuePath="id"
optionLabelPath="fullNameMU"
placeholder="Select one or more strains"
}}
</li>
<li>
<button class="action button-green smaller right" {{action 'selectAllStrains'}}>
Select All
</button>
<button class="action button-red smaller right" {{action 'deselectAllStrains'}}>
Deselect All
</button>
</li>
<li>
<label>Characteristics</label>
{{
select-2
multiple=true
content=characteristics
value=selectedCharacteristics
optionValuePath="id"
optionLabelPath="characteristicName"
placeholder="Select one or more characteristics"
}}
</li>
<li>
<button class="action button-green smaller right" {{action 'selectAllCharacteristics'}}>
Select All
</button>
<button class="action button-red smaller right" {{action 'deselectAllCharacteristics'}}>
Deselect All
</button>
</li>
<li>
<button type="submit" class="action button-gray smaller right">
Search
</button>
</li>
</ul>
</form>
</fieldset>
</div>
{{
protected/compare/select-form
characteristics=characteristics
strains=strains
selectedStrains=selectedStrains
selectedCharacteristics=selectedCharacteristics
on-search=(action "search")
update-strains=(action "updateStrainSelection")
update-characteristics=(action "updateCharacteristicSelection")
}}
{{outlet}}

View file

@ -1,6 +1,8 @@
import Ember from 'ember';
export default Ember.Route.extend({
const { Route } = Ember;
export default Route.extend({
beforeModel: function(transition) {
this._super(transition);
this.transitionTo('protected.compare');

View file

@ -1 +0,0 @@
Welcome

View file

@ -1,9 +1,12 @@
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
const { Route } = Ember;
export default Route.extend(AuthenticatedRouteMixin, {
actions: {
error: function() {
error: function(err) {
console.log(err);
this.transitionTo('/not-found');
},

View file

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

View file

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

View file

@ -1,35 +1,44 @@
import Ember from 'ember';
import ElevatedAccess from '../../../../mixins/elevated-access';
export default Ember.Route.extend({
currentUser: Ember.inject.service('session-account'),
const { Route } = Ember;
beforeModel: function(transition) {
this._super(transition);
this.get('currentUser.account').then((user) => {
if (user.get('isReader')) {
this.transitionTo('protected.strains.index');
}
});
},
export default Route.extend(ElevatedAccess, {
// Required for ElevatedAccess mixin
fallbackRouteBefore: 'protected.strains.index',
fallbackRouteAfter: 'protected.strains.show',
model: function(params) {
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
characteristics: this.store.findAll('characteristic'), // Need for dropdown
});
},
// Overriding afterModel because of RSVP hash
afterModel: function(models) {
if (!models.strain.get('canEdit')) {
this.transitionTo('strains.show', models.strain.get('id'));
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) {
controller.setProperties(models);
this.get('currentUser.account').then((user) => {
controller.set('metaData', user.get('metaData'));
});
controller.set('model', models.strain);
controller.set('speciesList', models.species);
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
strain=strain
species=species
canAdd=metaData.canAdd
save="save"
cancel="cancel"
strain=model
speciesList=speciesList
add-characteristic=(action "addCharacteristic")
allCharacteristics=allCharacteristics
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';
export default Ember.Route.extend({
currentUser: Ember.inject.service('session-account'),
const { Route } = Ember;
export default Route.extend({
model: function() {
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>
<h3>Total strains: {{model.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 |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>
{{
protected/strains/index/strain-table
strains=model
}}

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,9 @@
import Ember from 'ember';
import DeleteModel from '../../../../mixins/delete-model';
export default Ember.Controller.extend({
actions: {
delete: function() {
this.get('model').destroyRecord().then(() => {
this.transitionToRoute('protected.strains.index');
});
},
},
const { Controller } = Ember;
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';
export default Ember.Route.extend({
const { Route } = Ember;
export default Route.extend({
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">
<legend>
Strain {{model.strainNameMU}}
</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}}
{{
protected/strains/show/strain-card
strain=model
on-delete=(action 'delete')
}}

View file

@ -1,21 +1,106 @@
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: {
save: function() {
this.sendAction('save');
return this.attrs['on-save'](this.getProperties(this.get('propertiesList')));
},
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) {
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) {
this.set('strain.notes', value);
this.updateField('strain.notes', value);
},
},
});

View file

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

View file

@ -1,33 +1,23 @@
import Ember from 'ember';
import ajaxRequest from '../../../../utils/ajax-request';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
currentUser: Ember.inject.service('session-account'),
const { Controller, inject: { service } } = Ember;
passwordConfirm: null,
export default Controller.extend({
session: service(),
ajax: service(),
currentUser: service('session-account'),
actions: {
save: function() {
if (this.get('password') !== this.get('passwordConfirm')) {
this.get('flashMessages').clearMessages();
this.get('flashMessages').error("Password fields don't match");
return;
}
let url = `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/users/password`;
let options = {
method: 'POST',
data: {
id: this.get('currentUser.account.id'),
password: this.get('password'),
},
};
ajaxRequest(url, options, this.get('session'));
this.transitionToRoute('protected.users.index');
save: function(password) {
const id = this.get('currentUser.account.id');
const data = { id: id, password: password };
this.get('ajax').post('/users/password', { data: data });
this.transitionToRoute('protected.users.show', id);
this.get('flashMessages').information('Your password has been changed.');
},
cancel: function() {
this.transitionToRoute('protected.users.show', this.get('currentUser.account.id'));
},
},
});

View file

@ -0,0 +1,52 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
password: null,
passwordConfirm: null,
matches: false,
// Actions
"on-save": null,
"on-cancel": null,
updateField: function(property, value) {
this.set(property, value);
this.verifyPassword(this.get('password'), this.get('passwordConfirm'));
},
verifyPassword: function(password, passwordConfirm) {
if (password && passwordConfirm) {
if (password !== passwordConfirm) {
this.get('flashMessages').clearMessages();
this.get('flashMessages').error("Password fields don't match");
this.set('matches', false);
} else {
this.get('flashMessages').clearMessages();
this.set('matches', true);
}
}
},
actions: {
save: function() {
this.verifyPassword(this.get('password'), this.get('passwordConfirm'));
if (this.get('matches')) {
return this.attrs['on-save'](this.get('password'));
}
},
cancel: function() {
return this.attrs['on-cancel']();
},
passwordDidChange: function(value) {
this.updateField('password', value);
},
passwordConfirmDidChange: function(value) {
this.updateField('passwordConfirm', value);
},
},
});

View file

@ -0,0 +1,29 @@
<div class="grid-1">
<div class="span-1">
<fieldset>
<legend>Change password</legend>
<form {{action 'save' on='submit'}}>
<ul>
<li>
<label>New Password</label>
{{one-way-input type="password" class="password" value=password update=(action "passwordDidChange")}}
</li>
<li>
<label>New Password (confirm)</label>
{{one-way-input type="password" class="password-confirm" value=passwordConfirm update=(action "passwordConfirmDidChange")}}
</li>
<li>
<a class="button-red smaller" {{action 'cancel'}}>
Cancel
</a>
{{#if matches}}
<button type="submit" class="button-green smaller submit-password">
Submit
</button>
{{/if}}
</li>
</ul>
</form>
</fieldset>
</div>
</div>

View file

@ -1,16 +1,19 @@
import Ember from 'ember';
export default Ember.Route.extend({
currentUser: Ember.inject.service('session-account'),
const { Route, inject: { service } } = Ember;
export default Route.extend({
currentUser: service('session-account'),
beforeModel: function(transition) {
this._super(transition);
let user_id = transition.params['protected.users.changepassword'].user_id;
// Only the logged in user can change their password
const user_id = transition.params['protected.users.changepassword'].user_id;
this.get('currentUser.account').then((user) => {
if (user.get('id') !== user_id) {
this.transitionTo('protected.users.index');
this.transitionTo('protected.users.show', user.get('id'));
}
});
}

View file

@ -1,24 +1,5 @@
<div class="grid-1">
<div class="span-1">
<fieldset>
<legend>Change password</legend>
<form {{action 'save' on='submit'}}>
<ul>
<li>
<label>New Password</label>
{{input type="password" value=password}}
</li>
<li>
<label>New Password (confirm)</label>
{{input type="password" value=passwordConfirm}}
</li>
<li>
<button type="submit" class="button-green smaller">
Submit
</button>
</li>
</ul>
</form>
</fieldset>
</div>
</div>
{{
protected/users/changepassword/password-form
on-save=(action "save")
on-cancel=(action "cancel")
}}

View file

@ -1,40 +1,10 @@
import Ember from 'ember';
import ajaxError from '../../../../utils/ajax-error';
import SaveModel from '../../../../mixins/save-model';
export default Ember.Controller.extend({
actions: {
save: function() {
let user = this.get('model');
const { Controller } = Ember;
if (user.get('hasDirtyAttributes')) {
let attrs = user.changedAttributes(), roleChanged = false;
if (attrs.role) {
roleChanged = true;
}
user.save().then((user) => {
this.get('flashMessages').clearMessages();
if (roleChanged) {
// Need to clear the store so that canEdit and canAdd
// attributes reflect the new role.
this.get('store').unloadAll();
}
this.transitionToRoute('protected.users.show', user);
}, () => {
ajaxError(user.get('errors'), this.get('flashMessages'));
});
} else {
this.transitionToRoute('protected.users.show', user);
}
},
cancel: function() {
let user = this.get('model');
user.get('errors').clear();
user.rollbackAttributes();
this.transitionToRoute('protected.users.show', user);
},
},
export default Controller.extend(SaveModel, {
// Required for SaveModel mixin
fallbackRouteSave: 'protected.users.show',
fallbackRouteCancel: 'protected.users.show',
});

View file

@ -1,8 +1,12 @@
import Ember from 'ember';
export default Ember.Route.extend({
currentUser: Ember.inject.service('session-account'),
const { Route, inject: { service } } = Ember;
export default Route.extend({
currentUser: service('session-account'),
// Not using ElevatedAccess Mixin because the rules for viewing user accounts
// is slightly different.
beforeModel: function(transition) {
this._super(transition);
@ -16,7 +20,7 @@ export default Ember.Route.extend({
},
model: function(params) {
return this.store.findRecord('user', params.user_id, { reload: true });
return this.store.findRecord('user', params.user_id);
},
});

View file

@ -1,7 +1,6 @@
{{
protected/users/user-form
user=model
currentUser=currentUser.account
save="save"
cancel="cancel"
on-save=(action "save")
on-cancel=(action "cancel")
}}

View file

@ -1,7 +1,9 @@
import Ember from 'ember';
export default Ember.Route.extend({
currentUser: Ember.inject.service('session-account'),
const { Route, inject: { service } } = Ember;
export default Route.extend({
currentUser: service('session-account'),
beforeModel: function(transition) {
this._super(transition);

View file

@ -1,33 +1,6 @@
<h2>{{genus-name}} Users</h2>
<h3>Total users: {{model.length}}</h3>
<table class="flakes-table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Date Registered</th>
</tr>
</thead>
<tbody>
{{#each model as |row|}}
<tr>
<td>
{{#link-to 'protected.users.show' row}}
{{row.name}}
{{/link-to}}
</td>
<td>
{{row.email}}
</td>
<td>
{{row.fullRole}}
</td>
<td>
{{null-time row.createdAt 'LL'}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{
protected/users/index/users-table
users=model
}}

View file

@ -0,0 +1,7 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
users: null,
});

View file

@ -0,0 +1,33 @@
<h3 id="total-users">Total users: {{users.length}}</h3>
<table class="flakes-table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Date Registered</th>
</tr>
</thead>
<tbody>
{{#each users as |user|}}
<tr>
<td>
{{#link-to 'protected.users.show' user}}
{{user.name}}
{{/link-to}}
</td>
<td>
{{user.email}}
</td>
<td>
{{user.fullRole}}
</td>
<td>
{{null-time user.createdAt 'LL'}}
</td>
</tr>
{{/each}}
</tbody>
</table>

View file

@ -1,9 +1,9 @@
import Ember from 'ember';
import DeleteModel from '../../../../mixins/delete-model';
export default Ember.Controller.extend({
currentUser: Ember.inject.service('session-account'),
const { Controller } = Ember;
isUser: Ember.computed('model.id', 'currentUser.account.id', function() {
return this.get('model.id') === this.get('currentUser.account.id');
}),
export default Controller.extend(DeleteModel, {
// Required for DeleteModel mixin
transitionRoute: 'protected.index',
});

View file

@ -1,21 +1,25 @@
import Ember from 'ember';
export default Ember.Route.extend({
currentUser: Ember.inject.service('session-account'),
const { Route, inject: { service } } = Ember;
export default Route.extend({
currentUser: service('session-account'),
// Not using ElevatedAccess Mixin because the rules for viewing user accounts
// is slightly different.
beforeModel: function(transition) {
this._super(transition);
this.get('currentUser.account').then((currentUser) => {
let user_id = transition.params['protected.users.show'].user_id;
if (!currentUser.get('isAdmin') && currentUser.get('id') !== user_id) {
this.get('currentUser.account').then((user) => {
const user_id = transition.params['protected.users.show'].user_id;
if (!user.get('isAdmin') && user.get('id') !== user_id) {
this.transitionTo('protected.users.index');
}
});
},
model: function(params) {
return this.store.findRecord('user', params.user_id, { reload: true });
return this.store.findRecord('user', params.user_id);
},
});

View file

@ -1,53 +1,5 @@
<div class="span-1">
<fieldset class="flakes-information-box">
<legend>
{{model.name}}
</legend>
{{! ROW 1 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Email</dt>
<dd>
{{model.email}}
</dd>
</dl>
<dl class="span-1">
<dt>Role</dt>
<dd>
{{model.fullRole}}
</dd>
</dl>
</div>
{{! ROW 2 }}
<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>
<br>
<div class="grid-2 gutter-20">
{{#if isUser}}
<div class="span-1">
{{#link-to 'protected.users.changepassword' model.id class="button-gray smaller"}}
Change Password
{{/link-to}}
</div>
{{/if}}
<div class="span-1">
{{#if model.canEdit}}
{{#link-to 'protected.users.edit' model.id class="button-gray smaller"}}
Edit
{{/link-to}}
{{/if}}
</div>
</div>
{{
protected/users/show/user-card
user=model
on-delete=(action 'delete')
}}

View file

@ -0,0 +1,13 @@
import Ember from 'ember';
const { Component, computed, inject: { service } } = Ember;
export default Component.extend({
currentUser: service('session-account'),
user: null,
isUser: computed('user.id', 'currentUser.account.id', function() {
return this.get('user.id') === this.get('currentUser.account.id');
}),
});

View file

@ -0,0 +1,53 @@
<div class="span-1">
<fieldset class="flakes-information-box">
<legend>
{{user.name}}
</legend>
{{! ROW 1 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Email</dt>
<dd>
{{user.email}}
</dd>
</dl>
<dl class="span-1">
<dt>Role</dt>
<dd>
{{user.fullRole}}
</dd>
</dl>
</div>
{{! ROW 2 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Record Created</dt>
<dd>{{null-time user.createdAt 'LL'}}</dd>
</dl>
<dl class="span-1">
<dt>Record Updated</dt>
<dd>{{null-time user.updatedAt 'LL'}}</dd>
</dl>
</div>
</fieldset>
</div>
<br>
<div class="grid-2 gutter-20">
{{#if isUser}}
<div class="span-1">
{{#link-to 'protected.users.changepassword' user.id class="button-gray smaller"}}
Change Password
{{/link-to}}
</div>
{{/if}}
<div class="span-1">
{{#if user.canEdit}}
{{#link-to 'protected.users.edit' user.id class="button-gray smaller"}}
Edit
{{/link-to}}
{{/if}}
</div>
</div>

View file

@ -1,15 +1,61 @@
import Ember from 'ember';
import SetupMetaData from '../../../../mixins/setup-metadata';
export default Ember.Component.extend({
const { Component } = Ember;
export default Component.extend(SetupMetaData, {
// Read-only attributes
user: null,
isDirty: false,
roles: Ember.String.w('A R W'),
// Actions
"on-save": null,
"on-cancel": null,
"on-update": null,
// Property mapping
propertiesList: ['name', 'email', 'role'],
name: null,
email: null,
role: null,
resetOnInit: Ember.on('init', function() {
this.get('propertiesList').forEach((field) => {
const valueInUser = this.get('user').get(field);
this.set(field, valueInUser);
});
}),
updateField: function(property, value) {
this.set(property, value);
// Manually compare against passed in value
if (this.get('user').get(property) !== value) {
this.set('isDirty', true);
} else {
this.set('isDirty', false);
}
},
actions: {
save: function() {
this.sendAction('save');
return this.attrs['on-save'](this.getProperties(this.get('propertiesList')));
},
cancel: function() {
this.sendAction('cancel');
return this.attrs['on-cancel']();
},
}
nameDidChange: function(value) {
this.updateField('name', value);
},
emailDidChange: function(value) {
this.updateField('email', value);
},
roleDidChange: function(value) {
this.updateField('role', value);
},
},
});

View file

@ -1,29 +1,29 @@
<form class="grid-form" {{action 'save' on='submit'}}>
<fieldset>
<legend><em>{{user.name}}</em></legend>
<legend><em>{{name}}</em></legend>
<div data-row-span="1">
<div data-field-span="1">
<label>Name</label>
{{input value=user.name}}
{{one-way-input type="text" class="user-name" value=name update=(action "nameDidChange")}}
</div>
</div>
<div data-row-span="1">
<div data-field-span="1">
<label>Email</label>
{{input value=user.email}}
{{one-way-input type="text" class="email" value=email update=(action "emailDidChange")}}
</div>
</div>
<div data-row-span="1">
<div data-field-span="1">
<label>Role</label>
{{#if session.currentUser.isAdmin}}
<select onchange={{action (mut user.role) value="target.value"}}>
{{#if isAdmin}}
<select onchange={{action "roleDidChange" value="target.value"}}>
{{#each roles as |roleChoice|}}
<option value={{roleChoice}} selected={{equal user.role roleChoice}}>{{roleChoice}}</option>
<option value={{roleChoice}} selected={{equal role roleChoice}}>{{roleChoice}}</option>
{{/each}}
</select>
{{else}}
{{user.role}}
{{role}} {{!-- Not editable --}}
{{/if}}
</div>
</div>
@ -32,8 +32,8 @@
<a class="button-red smaller" {{action 'cancel'}}>
Cancel
</a>
{{#if user.hasDirtyAttributes}}
<button type="submit" class="button-green smaller">
{{#if isDirty}}
<button type="submit" class="button-green smaller save-user">
Save
</button>
{{/if}}