diff --git a/.jshintrc b/.jshintrc
index 32bd9c7..e964d3f 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,5 +1,6 @@
{
"predef": [
+ "server",
"document",
"window",
"-Promise",
diff --git a/.travis.yml b/.travis.yml
index 66dd107..2b4c290 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,5 @@
---
language: node_js
-node_js:
- - "0.12"
sudo: false
@@ -10,7 +8,12 @@ cache:
- node_modules
before_install:
- - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH
+ - mkdir -p ~/.nvm/versions
+ - source ~/.nvm/nvm.sh && nvm install 0.12.2
+ - mkdir travis-phantomjs
+ - wget https://s3.amazonaws.com/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2
+ - tar -xvf $PWD/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2 -C $PWD/travis-phantomjs
+ - export PATH=$PWD/travis-phantomjs:$PATH
- "npm config set spin false"
- "npm install -g npm@^2"
diff --git a/app/authenticators/jwt-resolved.js b/app/authenticators/jwt-resolved.js
index 4abf71b..568647d 100644
--- a/app/authenticators/jwt-resolved.js
+++ b/app/authenticators/jwt-resolved.js
@@ -23,7 +23,7 @@ export default BaseAuthenticator.extend({
return RSVP.resolve(data);
},
- invalidate: function(data) {
+ invalidate: function(/* data */) {
return RSVP.resolve();
},
});
diff --git a/app/helpers/null-time.js b/app/helpers/null-time.js
index 8d87835..1efd82f 100644
--- a/app/helpers/null-time.js
+++ b/app/helpers/null-time.js
@@ -1,5 +1,5 @@
import Ember from 'ember';
-import { formatDate } from '../utils/date-helpers';
+import formatDate from '../utils/date-helpers';
export default Ember.Helper.helper(function(params) {
let value = params[0],
diff --git a/app/mirage/config.js b/app/mirage/config.js
new file mode 100644
index 0000000..79cee5e
--- /dev/null
+++ b/app/mirage/config.js
@@ -0,0 +1,19 @@
+export default function() {
+ // Don't use mirage for development (for now)
+ this.urlPrefix = 'http://127.0.0.1:8901';
+ this.namespace = '/api';
+ this.passthrough();
+}
+
+export function testConfig() {
+ this.urlPrefix = 'https://bactdb-test.herokuapp.com';
+ this.namespace = '/api/hymenobacter';
+ this.timing = 0;
+
+ this.get('/users/:id');
+
+ this.get('/species');
+ this.post('/species');
+ this.get('/species/:id');
+ this.put('/species/:id');
+}
diff --git a/app/mirage/factories/species.js b/app/mirage/factories/species.js
new file mode 100644
index 0000000..faf07da
--- /dev/null
+++ b/app/mirage/factories/species.js
@@ -0,0 +1,12 @@
+import Mirage, { faker } from 'ember-cli-mirage';
+
+export default Mirage.Factory.extend({
+ speciesName() { return faker.lorem.words().join(' '); },
+ typeSpecies: faker.random.boolean(),
+ etymology: faker.lorem.sentences(),
+ genusName: 'hymenobacter',
+ strains: [],
+ totalStrains: 0,
+ sortOrder: faker.random.number(),
+ canEdit: faker.random.boolean(),
+});
diff --git a/app/mirage/factories/users.js b/app/mirage/factories/users.js
new file mode 100644
index 0000000..7e14018
--- /dev/null
+++ b/app/mirage/factories/users.js
@@ -0,0 +1,8 @@
+import Mirage, { faker } from 'ember-cli-mirage';
+
+export default Mirage.Factory.extend({
+ name() { return faker.name.firstName() + ' ' + faker.name.lastName(); },
+ email: faker.internet.email,
+ role: 'R',
+ canEdit: false,
+});
diff --git a/app/mirage/scenarios/default.js b/app/mirage/scenarios/default.js
new file mode 100644
index 0000000..e07271c
--- /dev/null
+++ b/app/mirage/scenarios/default.js
@@ -0,0 +1,7 @@
+export default function(/* server */) {
+
+ // Seed your development database using your factories. This
+ // data will not be loaded in your tests.
+
+ // server.createList('contact', 10);
+}
diff --git a/app/mixins/delete-model.js b/app/mixins/delete-model.js
new file mode 100644
index 0000000..e16fb76
--- /dev/null
+++ b/app/mixins/delete-model.js
@@ -0,0 +1,15 @@
+import Ember from 'ember';
+
+const { Mixin } = Ember;
+
+export default Mixin.create({
+ transitionRoute: null,
+
+ actions: {
+ delete: function() {
+ this.get('model').destroyRecord().then(() => {
+ this.transitionToRoute(this.get('transitionRoute'));
+ });
+ },
+ },
+});
diff --git a/app/mixins/elevated-access.js b/app/mixins/elevated-access.js
new file mode 100644
index 0000000..326042a
--- /dev/null
+++ b/app/mixins/elevated-access.js
@@ -0,0 +1,36 @@
+import Ember from 'ember';
+
+const { Mixin , inject: { service } } = Ember;
+
+export default Mixin.create({
+ currentUser: service('session-account'),
+
+ fallbackRouteBefore: null,
+ fallbackRouteAfter: null,
+
+ beforeModel: function(transition) {
+ this._super(transition);
+ this.get('currentUser.account').then((user) => {
+ if (user.get('isReader')) {
+ this.transitionTo(this.get('fallbackRouteBefore'));
+ }
+ });
+ },
+
+ afterModel: function(model) {
+ if (!model.get('isNew') && !model.get('canEdit')) {
+ this.transitionTo(this.get('fallbackRouteAfter'), model.get('id'));
+ }
+ },
+
+ actions: {
+ willTransition: function(/*transition*/) {
+ const controller = this.get('controller');
+ const model = controller.get('model');
+
+ if (model.get('isNew')) {
+ model.destroyRecord();
+ }
+ },
+ },
+});
diff --git a/app/mixins/save-model.js b/app/mixins/save-model.js
new file mode 100644
index 0000000..97cc97b
--- /dev/null
+++ b/app/mixins/save-model.js
@@ -0,0 +1,36 @@
+import Ember from 'ember';
+import ajaxError from '../utils/ajax-error';
+
+const { Mixin } = Ember;
+
+export default Mixin.create({
+ fallbackRoute: null,
+
+ actions: {
+ save: function(properties) {
+ const model = this.get('model');
+ const fallbackRoute = this.get('fallbackRoute');
+
+ model.setProperties(properties);
+
+ if (model.get('hasDirtyAttributes')) {
+ model.save().then((model) => {
+ this.transitionToRoute(fallbackRoute, model);
+ }, () => {
+ ajaxError(model.get('errors'), this.get('flashMessages'));
+ });
+ } else {
+ this.transitionToRoute(fallbackRoute, model);
+ }
+ },
+
+ cancel: function() {
+ const model = this.get('model');
+
+ model.get('errors').clear();
+ model.rollbackAttributes();
+
+ this.transitionToRoute(this.get('fallbackRoute'), model);
+ },
+ },
+});
diff --git a/app/pods/components/text-editor/component.js b/app/pods/components/text-editor/component.js
index 07c8f7a..01d00fd 100644
--- a/app/pods/components/text-editor/component.js
+++ b/app/pods/components/text-editor/component.js
@@ -3,7 +3,8 @@ import Ember from 'ember';
export default Ember.Component.extend({
quill: null,
- value: null, // passed in
+ value: null,
+ update: null,
didInsertElement: function() {
let quill = new Quill(`#${this.get('elementId')} .editor`, {
@@ -22,11 +23,10 @@ export default Ember.Component.extend({
quill.on('text-change', (delta, source) => {
if (source === 'user') {
- this.set('value', Ember.$(quill.getHTML()).html());
+ this.attrs['update'](Ember.$(quill.getHTML()).html());
}
});
this.set('quill', quill);
},
-
});
diff --git a/app/pods/protected/species/edit/controller.js b/app/pods/protected/species/edit/controller.js
index 186e7e4..5f7591b 100644
--- a/app/pods/protected/species/edit/controller.js
+++ b/app/pods/protected/species/edit/controller.js
@@ -1,30 +1,9 @@
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 species = this.get('model');
+const { Controller } = Ember;
- if (species.get('hasDirtyAttributes')) {
- species.save().then((species) => {
- this.transitionToRoute('protected.species.show', species);
- }, () => {
- ajaxError(species.get('errors'), this.get('flashMessages'));
- });
- } else {
- this.transitionToRoute('protected.species.show', species);
- }
- },
-
- cancel: function() {
- let species = this.get('model');
-
- species.get('errors').clear();
- species.rollbackAttributes();
-
- this.transitionToRoute('protected.species.show', species);
- },
-
- },
+export default Controller.extend(SaveModel, {
+ // Required for SaveModel mixin
+ fallbackRoute: 'protected.species.show',
});
diff --git a/app/pods/protected/species/edit/route.js b/app/pods/protected/species/edit/route.js
index 1f5b616..fd5b75c 100644
--- a/app/pods/protected/species/edit/route.js
+++ b/app/pods/protected/species/edit/route.js
@@ -1,28 +1,15 @@
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.species.index');
- }
- });
- },
+export default Route.extend(ElevatedAccess, {
+ // Required for ElevatedAccess mixin
+ fallbackRouteBefore: 'protected.species.index',
+ fallbackRouteAfter: 'protected.species.show',
- afterModel: function(species) {
- if (!species.get('canEdit')) {
- this.transitionTo('species.show', species.get('id'));
- }
- },
-
- setupController: function(controller, model) {
- controller.set('model', model);
- this.get('currentUser.account').then((user) => {
- controller.set('metaData', user.get('metaData'));
- });
+ model: function(params) {
+ return this.store.findRecord('species', params.species_id);
},
});
diff --git a/app/pods/protected/species/edit/template.hbs b/app/pods/protected/species/edit/template.hbs
index aa8272e..68fed98 100644
--- a/app/pods/protected/species/edit/template.hbs
+++ b/app/pods/protected/species/edit/template.hbs
@@ -1,7 +1,6 @@
{{
protected/species/species-form
species=model
- metaData=metaData
- save="save"
- cancel="cancel"
+ on-save=(action "save")
+ on-cancel=(action "cancel")
}}
diff --git a/app/pods/protected/species/index/controller.js b/app/pods/protected/species/index/controller.js
deleted file mode 100644
index 77916f1..0000000
--- a/app/pods/protected/species/index/controller.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import Ember from 'ember';
-
-export default Ember.Controller.extend({
- sortParams: ['speciesName', 'strainCount'],
- sortedSpecies: Ember.computed.sort('model', 'sortParams'),
-});
diff --git a/app/pods/protected/species/index/route.js b/app/pods/protected/species/index/route.js
index 89c832d..c30060f 100644
--- a/app/pods/protected/species/index/route.js
+++ b/app/pods/protected/species/index/route.js
@@ -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('species');
},
- setupController: function(controller, model) {
- controller.set('model', model);
- this.get('currentUser.account').then((user) => {
- controller.set('metaData', user.get('metaData'));
- });
- },
-
});
diff --git a/app/pods/protected/species/index/species-table/component.js b/app/pods/protected/species/index/species-table/component.js
new file mode 100644
index 0000000..fea0c08
--- /dev/null
+++ b/app/pods/protected/species/index/species-table/component.js
@@ -0,0 +1,20 @@
+import Ember from 'ember';
+
+const { Component, inject: { service }} = Ember;
+
+export default Component.extend({
+ currentUser: service('session-account'),
+
+ metaData: null,
+ species: null,
+
+ setupMetaDataOnInit: Ember.on('init', function() {
+ this.get('currentUser.account').then((user) => {
+ this.set('metaData', user.get('metaData'));
+ });
+ }),
+
+ sortParams: ['speciesName', 'strainCount'],
+ sortedSpecies: Ember.computed.sort('species', 'sortParams'),
+
+});
diff --git a/app/pods/protected/species/index/species-table/template.hbs b/app/pods/protected/species/index/species-table/template.hbs
new file mode 100644
index 0000000..f96f9cf
--- /dev/null
+++ b/app/pods/protected/species/index/species-table/template.hbs
@@ -0,0 +1,33 @@
+
Total species: {{species.length}}
+
+{{add-button label="Add Species" link="protected.species.new" canAdd=metaData.canAdd}}
+
+
+
+
+ Name |
+ Strains |
+
+
+
+ {{#each sortedSpecies as |species|}}
+
+
+
+ {{#link-to 'protected.species.show' species}}
+ {{species.speciesName}}
+ {{/link-to}}
+
+ |
+
+ {{#each species.strains as |strain index|}}
+ {{if index ","}}
+ {{#link-to 'protected.strains.show' strain.id}}
+ {{{strain.strainNameMU}}}
+ {{/link-to}}
+ {{/each}}
+ |
+
+ {{/each}}
+
+
diff --git a/app/pods/protected/species/index/template.hbs b/app/pods/protected/species/index/template.hbs
index 0774bb3..b3a3f8a 100644
--- a/app/pods/protected/species/index/template.hbs
+++ b/app/pods/protected/species/index/template.hbs
@@ -1,34 +1,6 @@
{{genus-name}} Species
-Total species: {{model.length}}
-{{add-button label="Add Species" link="protected.species.new" canAdd=metaData.canAdd}}
-
-
-
-
- Name |
- Strains |
-
-
-
- {{#each sortedSpecies as |species|}}
-
-
-
- {{#link-to 'protected.species.show' species}}
- {{species.speciesName}}
- {{/link-to}}
-
- |
-
- {{#each species.strains as |strain index|}}
- {{if index ","}}
- {{#link-to 'protected.strains.show' strain.id}}
- {{{strain.strainNameMU}}}
- {{/link-to}}
- {{/each}}
- |
-
- {{/each}}
-
-
+{{
+ protected/species/index/species-table
+ species=model
+}}
diff --git a/app/pods/protected/species/new/controller.js b/app/pods/protected/species/new/controller.js
index 6170616..5f7591b 100644
--- a/app/pods/protected/species/new/controller.js
+++ b/app/pods/protected/species/new/controller.js
@@ -1,29 +1,9 @@
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 species = this.get('model');
+const { Controller } = Ember;
- if (species.get('hasDirtyAttributes')) {
- species.save().then((species) => {
- this.transitionToRoute('protected.species.show', species.get('id'));
- }, () => {
- ajaxError(species.get('errors'), this.get('flashMessages'));
- });
- } else {
- species.destroyRecord().then(() => {
- this.transitionToRoute('protected.species.index');
- });
- }
- },
-
- cancel: function() {
- this.get('model').destroyRecord().then(() => {
- this.transitionToRoute('protected.species.index');
- });
- },
-
- },
+export default Controller.extend(SaveModel, {
+ // Required for SaveModel mixin
+ fallbackRoute: 'protected.species.show',
});
diff --git a/app/pods/protected/species/new/route.js b/app/pods/protected/species/new/route.js
index caf8dba..7ed36ec 100644
--- a/app/pods/protected/species/new/route.js
+++ b/app/pods/protected/species/new/route.js
@@ -1,30 +1,14 @@
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.species.index');
- }
- });
- },
+export default Route.extend(ElevatedAccess, {
+ // Required for ElevatedAccess mixin
+ fallbackRouteBefore: 'protected.species.index',
+ fallbackRouteAfter: 'protected.species.show',
model: function() {
return this.store.createRecord('species');
},
-
- actions: {
- willTransition: function(/*transition*/) {
- const controller = this.get('controller');
- const species = controller.get('model');
-
- if (species.get('isNew')) {
- species.destroyRecord();
- }
- },
- },
-
});
diff --git a/app/pods/protected/species/new/template.hbs b/app/pods/protected/species/new/template.hbs
index c3b8a05..68fed98 100644
--- a/app/pods/protected/species/new/template.hbs
+++ b/app/pods/protected/species/new/template.hbs
@@ -1,6 +1,6 @@
{{
protected/species/species-form
species=model
- save="save"
- cancel="cancel"
+ on-save=(action "save")
+ on-cancel=(action "cancel")
}}
diff --git a/app/pods/protected/species/show/controller.js b/app/pods/protected/species/show/controller.js
index b44b6a7..4a659e0 100644
--- a/app/pods/protected/species/show/controller.js
+++ b/app/pods/protected/species/show/controller.js
@@ -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.species.index');
- });
- },
- },
+const { Controller } = Ember;
+export default Controller.extend(DeleteModel, {
+ // Required for DeleteModel mixin
+ transitionRoute: 'protected.species.index',
});
diff --git a/app/pods/protected/species/show/route.js b/app/pods/protected/species/show/route.js
index 94f8361..c99362f 100644
--- a/app/pods/protected/species/show/route.js
+++ b/app/pods/protected/species/show/route.js
@@ -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('species', params.species_id, { reload: true });
+ return this.store.findRecord('species', params.species_id);
},
});
diff --git a/app/pods/protected/species/show/species-card/component.js b/app/pods/protected/species/show/species-card/component.js
new file mode 100644
index 0000000..1492c0b
--- /dev/null
+++ b/app/pods/protected/species/show/species-card/component.js
@@ -0,0 +1,14 @@
+import Ember from 'ember';
+
+const { Component } = Ember;
+
+export default Component.extend({
+ species: null,
+ "on-delete": null,
+
+ actions: {
+ deleteSpecies: function() {
+ return this.attrs['on-delete']();
+ },
+ },
+});
diff --git a/app/pods/protected/species/show/species-card/template.hbs b/app/pods/protected/species/show/species-card/template.hbs
new file mode 100644
index 0000000..fac8247
--- /dev/null
+++ b/app/pods/protected/species/show/species-card/template.hbs
@@ -0,0 +1,62 @@
+
+
+
+
+
+{{#if species.canEdit}}
+
+ {{#link-to 'protected.species.edit' species class="button-gray smaller"}}
+ Edit
+ {{/link-to}}
+ {{delete-button delete=(action 'deleteSpecies')}}
+{{/if}}
diff --git a/app/pods/protected/species/show/template.hbs b/app/pods/protected/species/show/template.hbs
index 841a671..d2656ec 100644
--- a/app/pods/protected/species/show/template.hbs
+++ b/app/pods/protected/species/show/template.hbs
@@ -1,62 +1,5 @@
-
-
-
-
-
-{{#if model.canEdit}}
-
- {{#link-to 'protected.species.edit' model class="button-gray smaller"}}
- Edit
- {{/link-to}}
- {{delete-button delete=(action 'delete')}}
-{{/if}}
+{{
+ protected/species/show/species-card
+ species=model
+ on-delete=(action 'delete')
+}}
diff --git a/app/pods/protected/species/species-form/component.js b/app/pods/protected/species/species-form/component.js
index a53a469..290e6e5 100644
--- a/app/pods/protected/species/species-form/component.js
+++ b/app/pods/protected/species/species-form/component.js
@@ -1,13 +1,71 @@
import Ember from 'ember';
-export default Ember.Component.extend({
+const { Component, inject: { service } } = Ember;
+
+export default Component.extend({
+ currentUser: service('session-account'),
+
+ // Read-only attributes
+ species: null,
+ isNew: null,
+ isDirty: false,
+
+ // Actions
+ "on-save": null,
+ "on-cancel": null,
+ "on-update": null,
+
+ // Property mapping
+ propertiesList: ['speciesName', 'typeSpecies', 'strains', 'etymology'],
+ speciesName: null,
+ typeSpecies: null,
+ strains: null,
+ etymology: null,
+
+ resetOnInit: Ember.on('init', function() {
+ this.get('propertiesList').forEach((field) => {
+ const valueInSpecies = this.get('species').get(field);
+ this.set(field, valueInSpecies);
+ });
+ // Read-only attributes
+ this.set('isNew', this.get('species.isNew'));
+ }),
+
+ setupMetaDataOnInit: Ember.on('init', function() {
+ this.get('currentUser.account').then((user) => {
+ this.set('metaData', user.get('metaData'));
+ });
+ }),
+
+ updateField: function(property, value) {
+ this.set(property, value);
+ // Manually compare against passed in value
+ if (this.get('species').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']();
},
- }
+
+ speciesNameDidChange: function(value) {
+ this.updateField('speciesName', value);
+ },
+
+ typeSpeciesDidChange: function() {
+ this.updateField('typeSpecies', !this.get('typeSpecies'));
+ },
+
+ etymologyDidChange: function(value) {
+ this.updateField('etymology', value);
+ },
+ },
});
diff --git a/app/pods/protected/species/species-form/template.hbs b/app/pods/protected/species/species-form/template.hbs
index 2b2ae76..888b03e 100644
--- a/app/pods/protected/species/species-form/template.hbs
+++ b/app/pods/protected/species/species-form/template.hbs
@@ -1,21 +1,22 @@