Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Almost finished implementation of edit run

  • Loading branch information...
commit ad9d537927928e4788c5a6ca14a60ecc32b47b1b 1 parent dd85506
@JanVanRyswyck authored
View
1  application.coffee
@@ -1,5 +1,6 @@
bootstrapper = require('./bootstrapper')
express = require('express')
+require('./extensions')
application = express.createServer()
View
7 bootstrapper.coffee
@@ -10,7 +10,7 @@ routes.runs = _.extend(require('./routes/runs'),
require('./routes/runs/edit'))
validators = {}
-validators.newRun = require('./validators/newrun')
+validators.run = require('./validators/run')
exports.bootstrap = (application) ->
@@ -21,6 +21,7 @@ exports.bootstrap = (application) ->
bootstrapExpress = (application) ->
application.set('view engine', 'jade')
application.use(express.bodyParser())
+ application.use(express.methodOverride())
application.use(express.static(__dirname + '/public'))
application.use(express.errorHandler( dumpExceptions: true, showStack: true ))
application.use(express.cookieParser())
@@ -29,10 +30,12 @@ bootstrapExpress = (application) ->
bootstrapRoutes = (application) ->
application.get('/', routes.index)
application.get('/runs/new', routes.runs.new)
- application.post('/runs', validators.newRun.validate, routes.runs.create)
+ application.post('/runs', validators.run.validate, routes.runs.create)
application.get('/runs/:year([0-9]{4})?', routes.runs.index)
+ application.put('/runs/:id([a-z0-9]{32})', validators.run.validate, routes.runs.update)
application.get('/runs/:id([a-z0-9]{32})', routes.runs.edit)
+
bootstrapCouchDB = ->
configuration.couchDBSettings((error, couchDB) ->
if(error)
View
5 data/runmapper.coffee
@@ -1,10 +1,11 @@
module.exports = class RunMapper
mapFrom: (document) ->
+ id: document._id
+ revision: document._rev
+ type: document.type
date: document.date
distance: document.distance
duration: document.duration
- id: document._id
- revision: document._rev
speed: document.speed
averageHeartRate: document.averageHeartRate
shoes: document.shoes
View
14 data/runs.coffee
@@ -1,5 +1,6 @@
cradle = require('cradle')
connectionManager = require('./connectionmanager')
+errors = require('./../errors')
RunMapper = require('./runmapper')
_ = require('underscore')
@@ -49,9 +50,14 @@ module.exports = class Runs
callback(error, runs)
)
- add: (newRun) ->
- _database.save(newRun,
+ save: (run, callback) ->
+ _database.save(run.id, run.revision, run,
(error, response) ->
- console.log(error)
- console.log(response)
+ if(error)
+ callback(error)
+
+ callback(error,
+ id: response.id
+ revision: response.revision
+ )
)
View
5 errors.coffee
@@ -6,4 +6,9 @@ class exports.ConfigurationError extends Error
class exports.DataError extends Error
constructor: (message, @error) ->
super message
+ Error.captureStackTrace(@, arguments.callee);
+
+class exports.PersistenceError extends Error
+ constructor: (message, @error) ->
+ super message
Error.captureStackTrace(@, arguments.callee);
View
15 extensions.coffee
@@ -0,0 +1,15 @@
+_ = require('underscore')
+
+_.mixin(
+ padLeft: (text, totalLength, paddingChar) ->
+ string = text.toString()
+ while (string.length < totalLength)
+ string = paddingChar + string
+ return string
+
+ getCurrentDate: () ->
+ currentDate = new Date()
+ return currentDate.getFullYear() +
+ '-' + _(currentDate.getMonth() + 1).padLeft(2, '0') +
+ '-' + _(currentDate.getDate()).padLeft(2, '0')
+)
View
79 routes/runs/edit.coffee
@@ -5,26 +5,87 @@ Shoes = require('./../../data/shoes')
exports.edit = (request, response) ->
runId = request.params.id
- renderViewForRun(runId, response)
+ renderViewForEditRun(runId, response)
-# exports.update = (request, response) ->
+exports.update = (request, response) ->
+ runId = request.params.id
+
+ if not request.form.isValid
+ renderViewForEditRun(runId, response, request.form.getErrors())
+ return
+
+ runs = new Runs()
-renderViewForRun = (runId, response, validationErrors) ->
step(
- loadData = ->
- runs = new Runs()
- runs.getById(runId, @.parallel())
+ getRun = ->
+ runs.getById(runId, @)
+
+ updateRun = (error, run) ->
+ if error
+ throw new errors.DataError('An error occured while loading the data for updating a run.', error)
+
+ applyChangesTo(run, request.form)
+ run.speed = calculateSpeedFor(run)
+
+ runs.save(run, @)
+ redirectToIndex = (error) ->
+ if error
+ throw new errors.PersistenceError('An error occured while saving a run in the data store.', error)
+
+ response.redirect('/runs')
+ )
+
+renderViewForEditRun = (runId, response, validationErrors) ->
+ step(
+ loadData = ->
+ # TODO: Retrieve ALL shoes !!!!
shoes = new Shoes()
shoes.getShoesInUse(@.parallel())
- renderView = (error, run, shoesInUse) ->
- if(error)
+ if not validationErrors
+ runs = new Runs()
+ runs.getById(runId, @.parallel())
+
+ renderView = (error, shoesInUse, run) ->
+ if error
throw new errors.DataError('An error occured while loading data for the edit run page.', error)
+ if validationErrors
+ run = mapRunFromErroneousInput(response.locals(), runId)
+
response.render('runs/edit',
- validationErrors: validationErrors or {}
run: run
shoesInUse: shoesInUse or []
+ validationErrors: validationErrors or {}
)
)
+
+applyChangesTo = (run, formData) ->
+ run.date = formData.date
+ run.distance = formData.distance
+ run.duration = createDurationFrom(formData)
+ run.averageHeartRate = formData.averageHeartRate
+ run.shoes = formData.shoes
+ run.comments = formData.comments
+
+#TODO: refactor into seperate module!!
+mapRunFromErroneousInput = (formData, runId) ->
+ date: formData.date
+ distance: formData.distance
+ duration: createDurationFrom(formData)
+ id: runId
+ averageHeartRate : formData.averageHeartRate
+ shoes: formData.shoes
+ comments: formData.comments
+
+createDurationFrom = (formData) ->
+ hours: formData.durationHours
+ minutes: formData.durationMinutes
+ seconds: formData.durationSeconds
+
+# TODO: refactor into domain object??
+calculateSpeedFor = (newRun) ->
+ durationInSeconds = (newRun.duration.hours * 3600) + (newRun.duration.minutes * 60) + newRun.duration.seconds
+ speed = (newRun.distance / durationInSeconds) * 3600
+ return parseFloat(speed.toFixed(2))
View
67 routes/runs/new.coffee
@@ -1,5 +1,6 @@
step = require('step')
errors = require('./../../errors')
+_ = require('underscore')
Runs = require('./../../data/runs')
Shoes = require('./../../data/shoes')
@@ -11,19 +12,20 @@ exports.create = (request, response) ->
renderViewForNewRun(response, request.form.getErrors())
return
- newRun =
- type: 'run'
- date: request.form.date
- distance: request.form.distance
- duration: createDurationFrom(request.form)
- averageHeartRate: request.form.averageHeartRate
- shoes: request.form.shoes
- comments: request.form.comments
+ step(
+ createRun = () ->
+ newRun = mapRunFromInput(request.form)
+ newRun.speed = calculateSpeedFor(newRun)
+
+ runs = new Runs()
+ runs.save(newRun, @)
+
+ redirectToIndex = (error) ->
+ if error
+ throw new errors.PersistenceError('An error occured while creating a new run in the data store.', error)
- newRun.speed = calculateSpeedFor(newRun)
-
- runs = new Runs()
- runs.add(newRun)
+ response.redirect('/runs')
+ )
renderViewForNewRun = (response, validationErrors) ->
step(
@@ -32,19 +34,46 @@ renderViewForNewRun = (response, validationErrors) ->
shoes.getShoesInUse(@)
renderView = (error, shoesInUse) ->
- if(error)
+ if error
throw new errors.DataError('An error occured while loading data for the new run page.', error)
- response.render('runs/new',
- validationErrors: validationErrors or {}
+ run = createDefaultRun()
+ if validationErrors
+ run = mapRunFromInput(response.locals())
+
+ response.render('runs/new',
+ run: run
shoesInUse: shoesInUse or []
+ validationErrors: validationErrors or {}
)
)
-createDurationFrom = (requestForm) ->
- hours: requestForm.durationHours
- minutes: requestForm.durationMinutes
- seconds: requestForm.durationSeconds
+# TODO: Create factory
+createDefaultRun = () ->
+ type: 'run'
+ date: _.getCurrentDate()
+ distance: ''
+ duration:
+ hours: ''
+ minutes: ''
+ seconds: ''
+ averageHeartRate: ''
+ shoes: -1
+ comments: ''
+
+mapRunFromInput = (formData) ->
+ type: 'run'
+ date: formData.date
+ distance: formData.distance
+ duration: createDurationFrom(formData)
+ averageHeartRate : formData.averageHeartRate
+ shoes: formData.shoes
+ comments: formData.comments
+
+createDurationFrom = (formData) ->
+ hours: formData.durationHours
+ minutes: formData.durationMinutes
+ seconds: formData.durationSeconds
calculateSpeedFor = (newRun) ->
durationInSeconds = (newRun.duration.hours * 3600) + (newRun.duration.minutes * 60) + newRun.duration.seconds
View
7 validators/newrun.coffee → validators/run.coffee
@@ -3,8 +3,8 @@ form = require('express-form')
exports.validate = form(
form.filter('date').trim()
form.validate('date')
- .regex(/^(0[1-9]|[12][0-9]|3[01])[- //.](0[1-9]|1[012])[- //.](19|20)\d\d$/,
- 'Please specify a valid date (e.g. 15/01/2012).')
+ .regex(/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/,
+ 'Please specify a valid date (e.g. 2012-01-15).')
form.filter('distance').toFloat()
form.validate('distance').isNumeric('Please specify a valid distance.')
@@ -27,7 +27,6 @@ exports.validate = form(
)
form.filter('averageHeartRate').ifNull(0).toInt()
-
- form.filter('comments').ifNull('')
form.validate('shoes').notRegex(/-1/, 'Please select a pair of shoes.')
+ form.filter('comments').ifNull('')
)
View
6 views/runs/edit.jade
@@ -1,4 +1,8 @@
.container
.content
.page-header
- h1.inline-title Edit Run
+ h1.inline-title Edit Run
+
+ form(method='post', action='/runs/#{ run.id }')
+ input(name='_method', value='PUT', type='hidden')
+ !=partial('runfields', { run: run, shoesInUse: shoesInUse, validationErrors: validationErrors })
View
46 views/runs/new.jade
@@ -4,48 +4,4 @@
h1.inline-title New Run
form(method='post', action='/runs')
- fieldset
- div(class='clearfix' + (validationErrors.date ? ' error' : ''))
- label(for='date') Date *
- .input
- input#date(type='text', name='date', class='span3', autocomplete='off', value=locals.date)
- span.help-inline #{validationErrors.date}
- div(class='clearfix' + (validationErrors.distance ? ' error' : ''))
- label(for='distance') Distance *
- .input
- input#distance(type='text', name='distance', class='span2', autocomplete='off', value=locals.distance)
- span.unit km
- span.help-inline #{validationErrors.distance}
- div(class='clearfix' + (validationErrors.duration ? ' error' : ''))
- label Duration *
- .input
- .inline-inputs
- input#hours(type='text', name='durationHours', class='span1', autocomplete='off', value=locals.durationHours)
- span.unit h
- input#minutes(type='text', name='durationMinutes', class='span1', autocomplete='off', value=locals.durationMinutes)
- span.unit min
- input#seconds(type='text', name='durationSeconds', class='span1', autocomplete='off', value=locals.durationSeconds)
- span.unit sec
- span.help-inline #{validationErrors.duration}
- div(class='clearfix')
- label(for='averageHeartRate') Avg. Heart Rate
- .input
- input#averageHeartRate(type='text', name='averageHeartRate', class='span2', autocomplete='off')
- span.unit bpm
- div(class='clearfix' + (validationErrors.shoes ? ' error' : ''))
- label(for='shoes') Shoes *
- .input
- select#shoes(name='shoes', class='span4')
- option(value = -1) Select a pair of shoes ...
- each pairOfShoes in shoesInUse
- option(value = pairOfShoes.id, selected=(pairOfShoes.id == locals.shoes)) #{pairOfShoes.name}
- span.help-inline #{validationErrors.shoes}
- div(class='clearfix')
- label(for='comments') Comments
- .input
- textarea#comments(name='comments', class='span8', rows='4', autocomplete='off')
-
- .actions
- button.btn.primary(type='submit') Submit
- |
- a.btn(href='/runs') Cancel
+ !=partial('runfields', { run: run, shoesInUse: shoesInUse, validationErrors: validationErrors })
View
45 views/runs/runfields.jade
@@ -0,0 +1,45 @@
+fieldset
+ div(class='clearfix' + (validationErrors.date ? ' error' : ''))
+ label(for='date') Date *
+ .input
+ input#date(type='text', name='date', class='span3', autocomplete='off', value=run.date)
+ span.help-inline #{validationErrors.date}
+ div(class='clearfix' + (validationErrors.distance ? ' error' : ''))
+ label(for='distance') Distance *
+ .input
+ input#distance(type='text', name='distance', class='span2', autocomplete='off', value=run.distance)
+ span.unit km
+ span.help-inline #{validationErrors.distance}
+ div(class='clearfix' + (validationErrors.duration ? ' error' : ''))
+ label Duration *
+ .input
+ .inline-inputs
+ input#hours(type='text', name='durationHours', class='span1', autocomplete='off', value=run.duration.hours)
+ span.unit h
+ input#minutes(type='text', name='durationMinutes', class='span1', autocomplete='off', value=run.duration.minutes)
+ span.unit min
+ input#seconds(type='text', name='durationSeconds', class='span1', autocomplete='off', value=run.duration.seconds)
+ span.unit sec
+ span.help-inline #{validationErrors.duration}
+ div(class='clearfix')
+ label(for='averageHeartRate') Avg. Heart Rate
+ .input
+ input#averageHeartRate(type='text', name='averageHeartRate', class='span2', autocomplete='off', value=run.averageHeartRate)
+ span.unit bpm
+ div(class='clearfix' + (validationErrors.shoes ? ' error' : ''))
+ label(for='shoes') Shoes *
+ .input
+ select#shoes(name='shoes', class='span4')
+ option(value = -1) Select a pair of shoes ...
+ each pairOfShoes in shoesInUse
+ option(value = pairOfShoes.id, selected=(pairOfShoes.id == run.shoes)) #{pairOfShoes.name}
+ span.help-inline #{validationErrors.shoes}
+ div(class='clearfix')
+ label(for='comments') Comments
+ .input
+ textarea#comments(name='comments', class='span8', rows='4', autocomplete='off', value=run.comments)
+
+.actions
+ button.btn.primary(type='submit') Submit
+ |
+ a.btn(href='/runs') Cancel
Please sign in to comment.
Something went wrong with that request. Please try again.