Skip to content

Commit 2440b0d

Browse files
committed
Merge pull request #60 from startersacademy/issue43_instructor
Issue43 instructor
2 parents 0b06476 + bc94f23 commit 2440b0d

15 files changed

Lines changed: 719 additions & 1 deletion
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<style type="text/css">
2+
.main {
3+
margin: auto;
4+
max-width: 980px;
5+
}
6+
input {
7+
margin: 0 0 1em 0;
8+
}
9+
</style>
10+
<section id="instructor">
11+
<div class="main container">
12+
<form action="#">
13+
<input class="textfield" id="firstName" value="<%- firstName %>" /><br/>
14+
<input class="textfield" id="lastName" value="<%- lastName %>" /><br/>
15+
<input class="textfield" id="skills" value="<%- skills %>" /><br/>
16+
<button class="i-save">Save</button>
17+
<button class="i-cancel">Cancel</button>
18+
</form>
19+
</div>
20+
</section>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
var Backbone = require('../vendor/index').Backbone;
4+
var $ = require('../vendor/index').$;
5+
var Model = require('./instructor.model');
6+
var View = require('./instructor.view');
7+
8+
module.exports = Backbone.Controller.extend({
9+
routes: {
10+
'instructors/:id': 'showInstructor'
11+
},
12+
initialize: function(){
13+
this.options.container = this.options.container || 'body';
14+
this.model = new Model();
15+
this.view = new View({model: this.model});
16+
},
17+
showInstructor: function(instructorId, cb){
18+
this.fetchModel(instructorId, function(err){
19+
var view;
20+
21+
this.remove();
22+
this.view = new View({model: this.model});
23+
24+
if (err){
25+
view = this.renderError();
26+
} else {
27+
view = this.renderView();
28+
}
29+
if (cb){
30+
cb(err, view);
31+
}
32+
33+
}.bind(this));
34+
},
35+
fetchModel: function(instructorId, cb){
36+
this.model.set({id: instructorId});
37+
this.model.fetch({
38+
success: function(model, response, options){
39+
//console.log(model);
40+
cb(null, model);
41+
},
42+
error: function(model, response, options){
43+
//console.error(response);
44+
cb(response, model);
45+
}
46+
});
47+
},
48+
renderToContainer: function(view){
49+
return $(this.options.container).html(view);
50+
},
51+
renderView: function(){
52+
this.renderToContainer(this.view.render().$el);
53+
return this.view;
54+
},
55+
renderError: function(){
56+
return this.renderToContainer(
57+
'<p>There was a problem rendering this instructor</p>');
58+
}
59+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<style type="text/css">
2+
.main {
3+
margin: auto;
4+
max-width: 980px;
5+
}
6+
#result {
7+
padding: 1em 0;
8+
}
9+
.success {
10+
color: green;
11+
}
12+
</style>
13+
<section id="instructor">
14+
<div class="main container">
15+
<h1><%- firstName %> <%- lastName %></h1>
16+
<p><%- skills %></p>
17+
<button class="i-edit">Edit</button>
18+
<button class="i-delete">Delete</button>
19+
<div id="result"></div>
20+
</div>
21+
</section>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
var Backbone = require('../vendor/index').Backbone;
4+
module.exports = Backbone.Model.extend({
5+
defaults: {
6+
firstName: '',
7+
lastName: '',
8+
skills: ''
9+
},
10+
urlRoot: '/api/instructors',
11+
initialize: function(){
12+
this.on('change', function(){
13+
this.trigger('foo', 'bar');
14+
});
15+
},
16+
validate: function(attrs){
17+
var errors = [];
18+
if (!attrs.firstName){
19+
errors.push('firstName cannot be empty');
20+
}
21+
if (!attrs.lastName){
22+
errors.push('lastName cannot be empty');
23+
}
24+
if (!attrs.skills){
25+
errors.push('skills cannot be empty');
26+
}
27+
return errors;
28+
}
29+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
var Backbone = require('../vendor/index').Backbone;
3+
var _ = require('../vendor/index')._;
4+
var $ = require('../vendor/index').$;
5+
var fs = require('fs'); //will be replaced by brfs in the browser
6+
// readFileSync will be evaluated statically so errors can't be caught
7+
var template = fs.readFileSync(__dirname + '/instructor.html', 'utf8');
8+
var editTemplate = fs.readFileSync(__dirname + '/editInstructor.html', 'utf8');
9+
10+
module.exports = Backbone.View.extend({
11+
className: 'instructor',
12+
template: _.template(template),
13+
editTemplate: _.template(editTemplate),
14+
events: {
15+
'click .i-delete': 'destroy',
16+
'click .i-edit': 'edit',
17+
'click .i-save': 'save',
18+
'click .i-cancel': 'cancel'
19+
},
20+
initialize: function(){
21+
this.listenTo(this.model, 'destroy', this.remove);
22+
this.listenTo(this.model, 'change', this.render);
23+
},
24+
render: function(){
25+
var context = this.model.toJSON();
26+
this.$el.html(this.template(context));
27+
28+
return this;
29+
},
30+
destroy: function(){
31+
this.model.destroy();
32+
},
33+
edit: function(e){
34+
var context = this.model.toJSON();
35+
this.$el.html(this.editTemplate(context));
36+
37+
return this;
38+
},
39+
save: function(e) {
40+
e.preventDefault(); // if there's no changes, do not do anything
41+
42+
var formData = {
43+
firstName: this.$('#firstName').val().trim(),
44+
lastName: this.$('#lastName').val().trim(),
45+
skills: this.$('#skills').val().trim()
46+
};
47+
var validate = {
48+
success: function() {
49+
$('#result').addClass('success')
50+
.html('Successfully updated instructor')
51+
.fadeIn().delay(4000).fadeOut();
52+
},
53+
error: function(model, error) {
54+
55+
}
56+
};
57+
58+
this.model.save(formData, validate);
59+
},
60+
cancel: function(e) {
61+
e.preventDefault(); // prevent event bubbling
62+
this.render();
63+
}
64+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use strict';
2+
3+
/*
4+
global jasmine, describe, it, expect, beforeEach, afterEach, xdescribe, xit,
5+
spyOn
6+
*/
7+
// Get the code you want to test
8+
var Controller = require('../instructor.controller');
9+
var $ = require('jquery');
10+
var matchers = require('jasmine-jquery-matchers');
11+
// Test suite
12+
console.log('test instructor.controller');
13+
describe('Instructor controller', function(){
14+
var controller;
15+
16+
beforeEach(function(){
17+
controller = new Controller();
18+
});
19+
20+
it('can be created', function(){
21+
expect(controller).toBeDefined();
22+
});
23+
24+
describe('when it is created', function(){
25+
26+
it('has the expected routes', function(){
27+
expect(controller.routes).toEqual(jasmine.objectContaining({
28+
'instructors/:id': 'showInstructor'
29+
}));
30+
});
31+
32+
it('without a container option, uses body as the container', function(){
33+
expect(controller.options.container).toEqual('body');
34+
});
35+
36+
it('with a container option, uses specified container', function(){
37+
var ctrl = new Controller({container: '.newcontainer'});
38+
expect(ctrl.options.container).toEqual('.newcontainer');
39+
});
40+
41+
});
42+
43+
describe('when calling showInstructor', function(){
44+
45+
beforeEach(function(){
46+
jasmine.addMatchers(matchers);
47+
});
48+
49+
var success = function(callbacks){
50+
controller.model.set({'firstName': 'valid firstName',
51+
'lastName': 'valid lastName', 'skills':'valid skills'});
52+
callbacks.success(controller.model);
53+
};
54+
55+
var err = function(callbacks){
56+
callbacks.error('error', controller.model);
57+
};
58+
59+
it('with a valid instructor id, fetches the model', function(){
60+
spyOn(controller.model, 'fetch').and.callFake(success);
61+
var cb = function(err, view){
62+
expect(err).toBeNull();
63+
expect(controller.model.get('firstName')).toEqual('valid firstName');
64+
expect(controller.model.get('lastName')).toEqual('valid lastName');
65+
expect(controller.model.get('skills')).toEqual('valid skills');
66+
};
67+
68+
controller.showInstructor(1, cb);
69+
70+
});
71+
72+
it('with a valid instructor id, renders the view', function(){
73+
spyOn(controller.model, 'fetch').and.callFake(success);
74+
spyOn(controller.view, 'render').and.callFake(function(){
75+
controller.view.$el = 'fake render';
76+
return controller.view;
77+
});
78+
var cb = function(err, view){
79+
expect($('body')).toHaveText('');
80+
expect(view.cid).toEqual(controller.view.cid);
81+
};
82+
controller.showInstructor(1, cb);
83+
});
84+
85+
it('with an invalid instructor id, renders an error message', function(){
86+
spyOn(controller.model, 'fetch').and.callFake(err);
87+
var cb = function(err, view){
88+
expect(err).toBeTruthy();
89+
expect($('body')).toHaveText(
90+
'There was a problem rendering this instructor');
91+
};
92+
controller.showInstructor('whatid', cb);
93+
});
94+
});
95+
});

0 commit comments

Comments
 (0)