-
<%- resourceType %>
-
by:
+
+
+
+
Learning Resources
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/spa/js/learning-resource/learning-resources.view.js b/client/spa/js/learning-resource/learning-resources.view.js
new file mode 100644
index 0000000..289f308
--- /dev/null
+++ b/client/spa/js/learning-resource/learning-resources.view.js
@@ -0,0 +1,217 @@
+'use strict';
+/*jslint browser: true*/
+
+var Backbone = require('../vendor/index').Backbone;
+var _ = require('../vendor/index')._;
+var $ = require('../vendor/index').$;
+var Model = require('./learning-resource.model');
+
+var fs = require('fs'); //will be replaced by brfs in the browser
+
+// readFileSync will be evaluated statically so errors can't be caught
+var template = fs.readFileSync(__dirname + '/learning-resources.html', 'utf8');
+
+var showMessage = function(type, msg) {
+ $('#msg').empty().addClass(type)
+ .html(msg).fadeIn().delay(2000)
+ .fadeOut('slow').queue(function(remove) {
+ $('#msg').removeClass(type);
+ remove();
+ });
+};
+
+var Add = Backbone.Model.extend({
+
+ urlRoot: '/learning-resources',
+
+ defaults: {
+ title: '',
+ resourceType: '',
+ authors: '',
+ description: ''
+ },
+
+});
+
+//Modal View
+
+var ResourceDialog = Backbone.View.extend({
+ className: 'modal fade',
+ attributes: {
+ tabindex: '-1',
+ role: 'dialog',
+ },
+
+ initialize: function() {
+ this.model = new Add();
+ this.temp = _.template($('#dialog-template').html());
+ },
+
+ events: {
+ 'click #lrs-add-save': 'saveNewResource',
+ 'click .close, #cancel': 'close'
+ },
+
+ render: function() {
+ var context = this.model.toJSON();
+ this.$el.html(this.temp(context)).appendTo(document.body);
+ this.$el.modal();
+ return this;
+ },
+
+ close: function() {
+ this.remove();
+ $('.modal').remove();
+ showMessage('alert-warning', 'Changes cancelled');
+ },
+
+ saveNewResource: function(){
+ var collection = this.collection;
+
+ var newResource = new Model();
+
+ var authorsFormat = function() {
+ var authors = [];
+ // console.log($('#auth').val().split(','));
+ // if ($('#lrs-add-authors').val() === '') authors = null;
+ // else {
+ // $.each($('#lrs-add-authors').val().split(','), function(key,value){
+ // console.log(value);
+ // authors.push(value.trim());
+ // });
+ // }
+ return authors;
+ };
+
+ var saveArgs = {
+ attributes: {
+ title: $('#lrs-add-title').val().trim(),
+ resourceType: $('#lrs-add-resourceType option:selected').val(),
+ description: $('#lrs-add-description').val().trim(),
+ authors: $('#lrs-add-authors').val().split(',')
+ },
+ options: {
+ success: function(response){
+ $('#lrs-dismiss').click();
+ setTimeout(function() {
+ collection.fetch();
+ // collection.add(saveArgs.attributes);
+ $('table tr td').find('[value="na"]').attr('value', response.id);
+ $('.modal').remove();
+ }, 450);
+ showMessage('alert-success', 'Successfully updated');
+ },
+ error: function(model, error){
+ //server response errors if no validations specified
+ }
+ }
+ };
+ newResource.save(saveArgs.attributes, saveArgs.options);
+
+ if (newResource.validationError) {
+ showMessage('alert-danger', newResource.validationError);
+ }
+ },
+
+});
+
+//Main Collection View
+
+module.exports = Backbone.View.extend({
+
+ className: 'learning-resources',
+
+ template: _.template(template),
+
+ events:{
+ 'click .sortById': 'sortById',
+ 'click .sortByTitle': 'sortByTitle',
+ 'click .sortByResourceType': 'sortByResourceType',
+ 'click .sortByAuthors': 'sortByAuthors',
+ 'click .sortByDescription': 'sortByDescription',
+ 'click .btn-lrs-add': 'renderModal',
+ 'click .btn-lrs-del': 'delConfirm',
+ 'click .lrs-select-all': 'selectAll'
+ },
+
+ initialize: function() {
+ this.listenTo(this.collection, 'add', function(){
+ this.render();
+ });
+ this.listenTo(this.collection, 'reset', function(){
+ this.render();
+ });
+ this.listenTo(this.collection, 'sort', function(){
+ this.render();
+ });
+ this.listenTo(this.collection, 'remove', function(){
+ this.render();
+ });
+ },
+
+ render: function() {
+ this.delegateEvents();
+ var context = this.collection;
+ this.$el.html(this.template(context));
+ return this;
+ },
+
+ sortByTitle: function(){
+ this.collection.trigger('sortByTitle');
+ this.render();
+ },
+
+ sortByResourceType: function(){
+ this.collection.trigger('sortByResourceType');
+ this.render();
+ },
+
+ sortByAuthors: function(){
+ this.collection.trigger('sortByAuthors');
+ this.render();
+ },
+
+ sortByDescription: function(){
+ this.collection.trigger('sortByDescription');
+ this.render();
+ },
+
+ renderModal: function(){
+ var modal = new ResourceDialog({
+ collection: this.collection
+ });
+ modal.render();
+ },
+
+ selectAll: function(){
+ if ($('.lrs-select-all')[0].checked === true) {
+ $('.lrs-check').each(function() {
+ this.checked = true;
+ });
+ }
+ else {
+ $('.lrs-check').each(function() {
+ this.checked = false;
+ });
+ }
+ },
+
+ delConfirm: function() {
+ var collection = this.collection;
+ var checkedIds = [];
+ if ($('.lrs-check:checked').length === 0) {
+ showMessage('alert-info', 'No resources selected');
+ }
+ else {
+ $('.lrs-check:checked').each(function () {
+ checkedIds.push($(this).val());
+ });
+ $.each(checkedIds, function(key, value) {
+ var item = collection.get({id:value});
+ item.destroy();
+ collection.remove({id:value});
+ });
+ }
+ },
+
+});
diff --git a/client/spa/js/learning-resource/spec/learning-resource.controller.spec.js b/client/spa/js/learning-resource/spec/learning-resource.controller.spec.js
index 02028dd..f8a78aa 100644
--- a/client/spa/js/learning-resource/spec/learning-resource.controller.spec.js
+++ b/client/spa/js/learning-resource/spec/learning-resource.controller.spec.js
@@ -28,7 +28,7 @@ describe('Learning resource controller', function(){
it('has the expected routes', function(){
expect(controller.routes).toEqual(jasmine.objectContaining({
- 'learning-resource/:id': 'showLearningResource'
+ 'learning-resources/:id': 'showLearningResource'
}));
});
@@ -80,9 +80,9 @@ describe('Learning resource controller', function(){
it('does has a previous view to remove', function(){
var oldView = controller.view;
- spyOn(oldView, 'destroy');
+ spyOn(oldView, 'remove');
controller.showLearningResource(222);
- expect(oldView.destroy).toHaveBeenCalled();
+ expect(oldView.remove).toHaveBeenCalled();
});
});
diff --git a/client/spa/js/learning-resource/spec/learning-resource.view.spec.js b/client/spa/js/learning-resource/spec/learning-resource.view.spec.js
index 4add979..fc4c1aa 100644
--- a/client/spa/js/learning-resource/spec/learning-resource.view.spec.js
+++ b/client/spa/js/learning-resource/spec/learning-resource.view.spec.js
@@ -101,7 +101,7 @@ describe('Learning resource view ', function(){
view.render();
view.$('#title').val('changed title');
view.$('#desc').val('changed description');
- view.$('#authors').val('sis');
+ view.$('#auth').val('sis');
view.$('#resourceType option:selected').val('link');
spyOn(view, 'save').and.callThrough();
view.delegateEvents();
diff --git a/client/spa/js/learning-resource/spec/learning-resources.collection.spec.js b/client/spa/js/learning-resource/spec/learning-resources.collection.spec.js
new file mode 100644
index 0000000..cfa24fd
--- /dev/null
+++ b/client/spa/js/learning-resource/spec/learning-resources.collection.spec.js
@@ -0,0 +1,122 @@
+/*
+global jasmine, describe, it, expect, beforeEach, afterEach, xdescribe, xit,
+spyOn
+*/
+
+// Get the code you want to test
+var Collection = require('../learning-resources.collection');
+
+// Test suite
+console.log('test learning-resources.collection');
+describe('Learning resources collection ', function(){
+
+ var collection;
+ var modelA;
+ var modelB;
+ var modelC;
+
+ beforeEach(function(){
+ // Set up test data
+ modelA = {
+ id: 2,
+ title: 'A',
+ resourceType: 'link',
+ description: 'reading',
+ authors: 'mom'
+ };
+ modelB = {id: 0,
+ title: 'M',
+ resourceType: 'presentation',
+ description: 'mixing',
+ authors: 'bro'
+ };
+ modelC = {id: 1,
+ title: 'X',
+ resourceType: 'document',
+ description: 'listing',
+ authors: 'hass'
+ };
+
+ });
+
+
+ describe('when models are added to the collection ', function(){
+
+ beforeEach(function(){
+ collection = new Collection();
+
+ collection.add([
+ modelC,
+ modelB,
+ modelA
+ ],
+ {silent: false} // Set to true to suppress add event
+ );
+
+ });
+
+
+ it('orders the models by the contact id', function(){
+ expect(collection.at(0).get('id')).toEqual(modelC.id);
+ expect(collection.at(1).get('id')).toEqual(modelB.id);
+ expect(collection.at(2).get('id')).toEqual(modelA.id);
+ });
+
+ });
+
+ describe('when the collection interacts with the server', function(){
+
+ it('fetches from the correct url', function(){
+ collection = new Collection();
+ expect(collection.url).toEqual('/api/learning-resources/');
+ });
+
+ });
+
+ describe('when a sort event is triggered', function(){
+
+ beforeEach(function(){
+ collection = new Collection();
+
+ collection.add([
+ modelC,
+ modelB,
+ modelA
+ ],
+ {silent: false} // Set to true to suppress add event
+ );
+
+ });
+
+ it('sorts by title', function(){
+ collection.trigger('sortByTitle');
+ expect(collection.at(0).get('title')).toEqual(modelA.title);
+ expect(collection.at(1).get('title')).toEqual(modelB.title);
+ expect(collection.at(2).get('title')).toEqual(modelC.title);
+ });
+
+ it('sorts by resource type', function(){
+ collection.trigger('sortByResourceType');
+ expect(collection.at(0).get('resourceType')).toEqual(modelC.resourceType);
+ expect(collection.at(1).get('resourceType')).toEqual(modelA.resourceType);
+ expect(collection.at(2).get('resourceType')).toEqual(modelB.resourceType);
+ });
+
+ it('sorts by authors', function(){
+ collection.trigger('sortByAuthors');
+ expect(collection.at(0).get('authors')).toEqual(modelB.authors);
+ expect(collection.at(1).get('authors')).toEqual(modelC.authors);
+ expect(collection.at(2).get('authors')).toEqual(modelA.authors);
+ });
+
+ it('sorts by description', function(){
+ collection.trigger('sortByDescription');
+ expect(collection.at(0).get('description')).toEqual(modelC.description);
+ expect(collection.at(1).get('description')).toEqual(modelB.description);
+ expect(collection.at(2).get('description')).toEqual(modelA.description);
+ });
+
+ });
+
+});
+
diff --git a/client/spa/js/learning-resource/spec/learning-resources.controller.spec.js b/client/spa/js/learning-resource/spec/learning-resources.controller.spec.js
new file mode 100644
index 0000000..51d5539
--- /dev/null
+++ b/client/spa/js/learning-resource/spec/learning-resources.controller.spec.js
@@ -0,0 +1,124 @@
+/*
+global jasmine, describe, it, expect, beforeEach, afterEach, xdescribe, xit,
+spyOn
+*/
+
+// Get the code you want to test
+var Backbone = require('../../vendor/index').Backbone;
+var Controller = require('../learning-resources.controller');
+var $ = require('jquery');
+var matchers = require('jasmine-jquery-matchers');
+
+// Test suite
+console.log('test learning-resources.controller');
+describe('Learning resources controller', function(){
+
+ var controller;
+
+ beforeEach(function(){
+ controller = new Controller();
+ });
+
+ it('can be created', function(){
+ expect(controller).toBeDefined();
+ });
+
+ describe('when it is created', function(){
+
+ it('has the expected routes', function(){
+ expect(controller.routes).toEqual(jasmine.objectContaining({
+ 'learning-resources': 'showLearningResources'
+ }));
+ });
+
+ it('without a container option, uses body as the container', function(){
+ expect(controller.options.container).toEqual('body');
+ });
+
+ it('with a container option, uses specified container', function(){
+ var ctrl = new Controller({container: '.newcontainer'});
+ expect(ctrl.options.container).toEqual('.newcontainer');
+ });
+ });
+
+ describe('when asked to showLearningResources', function(){
+
+ beforeEach(function(){
+ jasmine.addMatchers(matchers);
+
+ });
+
+ describe('and fetch is successful', function(){
+
+ beforeEach(function(){
+ spyOn(Backbone.Collection.prototype, 'fetch').and.callFake(
+ function(options){
+ options.success();
+ }
+ );
+ });
+
+ it('sets up the collection if it is not already', function(){
+ expect(controller.collection).not.toBeDefined();
+ controller.showLearningResources();
+ expect(controller.collection).toBeDefined();
+ });
+
+ it('uses the existing collection if it is already setup', function(){
+ controller.showLearningResources();
+ controller.collection.add({id: 'xyz'});
+ controller.showLearningResources();
+ expect(controller.collection.at(0).get('id')).toEqual('xyz');
+ });
+
+ it('fetches data for the collection', function(){
+ controller.showLearningResources();
+ expect(controller.collection.fetch).toHaveBeenCalled();
+ });
+
+ it('sets up the view if it is not already', function(){
+ expect(controller.view).not.toBeDefined();
+ controller.showLearningResources();
+ expect(controller.view).toBeDefined();
+ });
+
+ it('uses the existing view if it is already setup', function(){
+ controller.showLearningResources();
+ controller.view.test = true;
+ controller.showLearningResources();
+ expect(controller.view.test).toBeTruthy();
+ });
+
+ it('renders the view to the correct container', function() {
+ spyOn(controller, 'renderView').and.callThrough();
+ controller.showLearningResources();
+ var returnedView = controller.renderView.calls.mostRecent().object.view;
+ expect(returnedView).toEqual(controller.view);
+ expect($('.page-title')).toHaveText('Learning Resources');
+ });
+
+ });
+
+ describe('and fetch errors', function(){
+
+ beforeEach(function(){
+ spyOn(Backbone.Collection.prototype, 'fetch').and.callFake(
+ function(options){
+ options.error();
+ }
+ );
+ });
+
+ it('renders error', function(){
+ controller.showLearningResources();
+ expect($('body')).toHaveText(
+ 'There was a problem rendering learning resources'
+ );
+ });
+
+ });
+
+ });
+
+});
+
diff --git a/client/spa/js/learning-resource/spec/learning-resources.view.spec.js b/client/spa/js/learning-resource/spec/learning-resources.view.spec.js
new file mode 100644
index 0000000..6f17424
--- /dev/null
+++ b/client/spa/js/learning-resource/spec/learning-resources.view.spec.js
@@ -0,0 +1,221 @@
+/*
+global jasmine, describe, it, expect, beforeEach, afterEach, xdescribe, xit,
+spyOn
+*/
+
+// Get the code you want to test
+var View = require('../learning-resources.view.js');
+var matchers = require('jasmine-jquery-matchers');
+var _ = require('../../vendor/index')._;
+var Backbone = require('../../vendor/index').Backbone;
+
+// Test suite
+console.log('test learning-resources.view');
+describe('Learning resources view ', function(){
+
+ var model;
+ var collection;
+ var view;
+
+ beforeEach(function(){
+ // Add some convenience tests for working with the DOM
+ jasmine.addMatchers(matchers);
+
+ var Model = Backbone.Model.extend({});
+ var Collection = Backbone.Collection.extend({model: Model});
+ // Needs to have the fields required by the template
+ model = new Model({
+ title: 'Meow',
+ resourceType: 'presentation',
+ description: 'Purrr',
+ authors: 'Mr. Meowmers'
+ });
+
+ collection = new Collection(model);
+
+ view = new View({
+ collection: collection
+ });
+ });
+
+ describe('when the view is instantiated ', function() {
+
+ it('creates the correct element', function () {
+ // Element has to be uppercase
+ expect(view.el.nodeName).toEqual('DIV');
+ });
+
+ it('sets the correct class', function () {
+ view.render();
+ expect(view.$el).toHaveClass('learning-resources');
+ });
+
+ });
+
+ describe('when collection events happen', function(){
+
+ beforeEach(function () {
+ spyOn(view, 'render').and.callThrough();
+ });
+
+ it('renders when something is added to the collection', function(){
+ collection.trigger('add');
+ expect(view.render).toHaveBeenCalled();
+ });
+
+ it('renders when the collection is reset', function(){
+ collection.trigger('reset');
+ expect(view.render).toHaveBeenCalled();
+ });
+
+ it('renders when the collection is sorted', function(){
+ collection.trigger('sort');
+ expect(view.render).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('when the view is rendered', function(){
+
+ it('returns the view object', function(){
+ expect(view.render()).toEqual(view);
+ });
+ it('produces the correct HTML', function(){
+ view.render();
+ expect(view.$el[0]).toHaveText('Meow');
+ });
+
+ });
+
+ // describe('when the user clicks on the Sort By Id button ', function(){
+
+ // beforeEach(function(){
+ // view.render();
+ // });
+
+ // it('triggers the sortById event on the collection', function(){
+ // var spy = jasmine.createSpy('sortById');
+ // collection.on('sortById', spy);
+
+ // view.$('.sortById').trigger('click');
+
+ // expect(spy).toHaveBeenCalled();
+
+ // });
+
+ // it('renders the view', function(){
+ // spyOn(view, 'render');
+
+ // view.$('.sortById').trigger('click');
+
+ // expect(view.render).toHaveBeenCalled();
+ // });
+
+ // });
+
+ describe('when the user clicks on the Title header ', function(){
+
+ beforeEach(function(){
+ view.render();
+ });
+
+ it('triggers the sortByTitle event on the collection', function(){
+ var spy = jasmine.createSpy('sortByTitle');
+ collection.on('sortByTitle', spy);
+
+ view.$('.sortByTitle').trigger('click');
+
+ expect(spy).toHaveBeenCalled();
+
+ });
+
+ it('renders the view', function(){
+ spyOn(view, 'render');
+
+ view.$('.sortByTitle').trigger('click');
+
+ expect(view.render).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('when the user clicks on the Type header ', function(){
+
+ beforeEach(function(){
+ view.render();
+ });
+
+ it('triggers the sortByTitle event on the collection', function(){
+ var spy = jasmine.createSpy('sortByResourceType');
+ collection.on('sortByResourceType', spy);
+
+ view.$('.sortByResourceType').trigger('click');
+
+ expect(spy).toHaveBeenCalled();
+
+ });
+
+ it('renders the view', function(){
+ spyOn(view, 'render');
+
+ view.$('.sortByResourceType').trigger('click');
+
+ expect(view.render).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('when the user clicks on the Authors header ', function(){
+
+ beforeEach(function(){
+ view.render();
+ });
+
+ it('triggers the sortByTitle event on the collection', function(){
+ var spy = jasmine.createSpy('sortByAuthors');
+ collection.on('sortByAuthors', spy);
+
+ view.$('.sortByAuthors').trigger('click');
+
+ expect(spy).toHaveBeenCalled();
+
+ });
+
+ it('renders the view', function(){
+ spyOn(view, 'render');
+
+ view.$('.sortByAuthors').trigger('click');
+
+ expect(view.render).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('when the user clicks on the Description header ', function(){
+
+ beforeEach(function(){
+ view.render();
+ });
+
+ it('triggers the sortByDescription event on the collection', function(){
+ var spy = jasmine.createSpy('sortByDescription');
+ collection.on('sortByDescription', spy);
+
+ view.$('.sortByDescription').trigger('click');
+
+ expect(spy).toHaveBeenCalled();
+
+ });
+
+ it('renders the view', function(){
+ spyOn(view, 'render');
+
+ view.$('.sortByDescription').trigger('click');
+
+ expect(view.render).toHaveBeenCalled();
+ });
+
+ });
+
+});
+
diff --git a/client/spa/js/main.js b/client/spa/js/main.js
index b1b493f..bcebed4 100644
--- a/client/spa/js/main.js
+++ b/client/spa/js/main.js
@@ -5,12 +5,15 @@ window.Backbone = require('./vendor').Backbone;
// Include your code
var Instructor = require('./instructor/instructor.controller');
var Resource = require('./learning-resource/learning-resource.controller');
+var Resources = require('./learning-resource/learning-resources.controller');
+
// Initialize it
window.instructor = new Instructor({router:true, container: 'body'});
window.resource = new Resource({router:true, container: 'body'});
+window.resources = new Resources({router:true, container: 'body'});
// Additional modules go here
-
+/* global window require */
// This should be the last line
window.Backbone.history.start();
diff --git a/client/spa/js/vendor/index.js b/client/spa/js/vendor/index.js
index 2b7cbe9..d12159b 100644
--- a/client/spa/js/vendor/index.js
+++ b/client/spa/js/vendor/index.js
@@ -1,4 +1,5 @@
'use strict';
+/*jslint browser: true*/
// Expose underscore
exports._ = require('underscore');
@@ -9,7 +10,9 @@ var Backbone = require('backbone');
var Controller = require('backbone.controller');
// Assign and expose jquery reference since we are using browserify
-Backbone.$ = exports.$ = require('jquery');
+Backbone.$ = exports.$ = window.jQuery = require('jquery');
+
+require('bootstrap');
// Help prevent 'ghost views'
Backbone.View.prototype.close = function(){
diff --git a/common/models/contact.js b/common/models/contact.js
new file mode 100644
index 0000000..d564eba
--- /dev/null
+++ b/common/models/contact.js
@@ -0,0 +1,3 @@
+module.exports = function(Contact) {
+ //Contact.validatesInclusionOf('enumTest', {in: ['contractor', 'employee']});
+};
diff --git a/common/models/contact.json b/common/models/contact.json
new file mode 100644
index 0000000..75ed28b
--- /dev/null
+++ b/common/models/contact.json
@@ -0,0 +1,15 @@
+{
+ "name": "contact",
+ "base": "PersistedModel",
+ "idInjection": true,
+ "properties": {
+ "name": {
+ "type": "string",
+ "required": true
+ }
+ },
+ "validations": [],
+ "relations": {},
+ "acls": [],
+ "methods": []
+}
diff --git a/package.json b/package.json
index 48f4df2..87d36c1 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"backbone": "^1.1.2",
"backbone.controller": "^0.3.1",
"body-parser": "^1.10.0",
+ "bootstrap": "^3.3.2",
"brfs": "^1.2.0",
"browserify-middleware": "^4.1.0",
"compression": "^1.0.3",
diff --git a/server/boot/spa.js b/server/boot/spa.js
index c795bbb..1933dba 100644
--- a/server/boot/spa.js
+++ b/server/boot/spa.js
@@ -32,6 +32,3 @@ module.exports = function mountApps(server) {
server.use('/spa', router);
};
-
-
-