Skip to content

Commit

Permalink
Refactor voting app to Ember.js
Browse files Browse the repository at this point in the history
The initial server-side app had the data model I wanted, but I did not
like how it used one big form with a bunch of hacky view logic for the
actual voting process. This refactors it to an Ember.js app to make it
easier to add the voting features I wanted.
  • Loading branch information
beerlington committed Mar 9, 2014
1 parent 1638c6a commit a304ce6
Show file tree
Hide file tree
Showing 62 changed files with 2,061 additions and 191 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ language: ruby
notifications:
email: false
rvm:
- 2.1.0
- 2.1.1
8 changes: 8 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ gem "classy_enum", "~> 3.4.0"
# Markdown processor
gem "redcarpet", "~> 3.1.0"

# Ember dependencies
gem "ember-rails", "~> 0.14.1"
gem "ember-source", "~> 1.5.0.beta.3"
gem "ember-data-source", "~> 1.0.0.beta.7"
gem "handlebars-source", "~> 1.3.0"

group :production do
gem 'pg', '~> 0.17.1'
gem 'rails_12factor'
Expand All @@ -62,4 +68,6 @@ end

group :test do
gem 'capybara', '~> 2.2'
gem 'poltergeist', '~> 1.5'
gem 'launchy'
end
36 changes: 36 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ GEM
erubis (~> 2.7.0)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
active_model_serializers (0.8.1)
activemodel (>= 3.0)
activemodel (4.0.3)
activesupport (= 4.0.3)
builder (~> 3.1.0)
Expand All @@ -43,6 +45,7 @@ GEM
multi_json (~> 1.3)
thread_safe (~> 0.1)
tzinfo (~> 0.3.37)
addressable (2.3.5)
arbre (1.0.1)
activesupport (>= 3.0.0)
arel (4.0.2)
Expand All @@ -51,6 +54,10 @@ GEM
json (~> 1.4)
nokogiri (>= 1.4.4)
uuidtools (~> 2.1)
barber (0.4.2)
ember-source
execjs
handlebars-source
bcrypt-ruby (3.1.2)
bourbon (3.1.8)
sass (>= 3.2.0)
Expand All @@ -66,6 +73,7 @@ GEM
activerecord (>= 3.0)
climate_control (0.0.3)
activesupport (>= 3.0)
cliver (0.3.2)
cocaine (0.5.3)
climate_control (>= 0.0.3, < 1.0)
coffee-rails (4.0.1)
Expand All @@ -88,6 +96,19 @@ GEM
railties (>= 3.2.6, < 5)
thread_safe (~> 0.1)
warden (~> 1.2.3)
ember-data-source (1.0.0.beta.7)
ember-source
ember-rails (0.14.1)
active_model_serializers
barber (>= 0.4.1)
ember-data-source
ember-source
execjs (>= 1.2)
handlebars-source
jquery-rails (>= 1.0.17)
railties (>= 3.1)
ember-source (1.5.0.beta.3)
handlebars-source (~> 1.0)
erubis (2.7.0)
execjs (2.0.2)
faraday (0.8.9)
Expand All @@ -100,6 +121,7 @@ GEM
foundation-rails (5.1.1.0)
railties (>= 3.1.0)
sass (>= 3.2.0)
handlebars-source (1.3.0)
has_scope (0.6.0.rc)
actionpack (>= 3.2, < 5)
activesupport (>= 3.2, < 5)
Expand All @@ -124,6 +146,8 @@ GEM
kaminari (0.15.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
launchy (2.4.2)
addressable (~> 2.3)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
Expand Down Expand Up @@ -157,6 +181,11 @@ GEM
cocaine (~> 0.5.3)
mime-types
pg (0.17.1)
poltergeist (1.5.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
polyamorous (0.6.4)
activerecord (>= 3.0)
polyglot (0.3.4)
Expand Down Expand Up @@ -226,6 +255,7 @@ GEM
uuidtools (2.1.4)
warden (1.2.3)
rack (>= 1.0)
websocket-driver (0.3.2)
xpath (2.0.0)
nokogiri (~> 1.3)

Expand All @@ -239,14 +269,20 @@ DEPENDENCIES
classy_enum (~> 3.4.0)
coffee-rails (~> 4.0.0)
debugger
ember-data-source (~> 1.0.0.beta.7)
ember-rails (~> 0.14.1)
ember-source (~> 1.5.0.beta.3)
foundation-icons-sass-rails (~> 3.0.0)
foundation-rails
handlebars-source (~> 1.3.0)
jbuilder (~> 1.2)
jquery-rails
launchy
newrelic_rpm
omniauth-github (~> 1.1)
paperclip (~> 3.5.2)
pg (~> 0.17.1)
poltergeist (~> 1.5)
rails (~> 4.0.2)
rails_12factor
redcarpet (~> 3.1.0)
Expand Down
1 change: 0 additions & 1 deletion app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@
//= require jquery
//= require jquery_ujs
//= require foundation
//= require_tree .

$(document).foundation();
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Proposals.CurrentUserController = Ember.Controller.extend({})

22 changes: 22 additions & 0 deletions app/assets/javascripts/controllers/index_controller.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Proposals.IndexController = Ember.Controller.extend
# TODO: Figure out how to DRY this up

roundoneVotes: (->
round = @get('rounds').findBy('name', 'one')
votes = @get('votes').filterBy('round', 'one').sortBy('proposal.title')

for item in [(votes.length + 1)..round.get('totalVotes')] by 1
votes.push {}

votes
).property('rounds', 'votes.@each')

roundtwoVotes: (->
round = @get('rounds').findBy('name', 'two')
votes = @get('votes').filterBy('round', 'two').sortBy('proposal.title')

for item in [(votes.length + 1)..round.get('totalVotes')] by 1
votes.push {}

votes
).property('rounds', 'votes.@each')
49 changes: 49 additions & 0 deletions app/assets/javascripts/controllers/proposal_controller.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Proposals.ProposalController = Ember.ObjectController.extend
abstractActive: true
notesActive: false
pitchActive: false
submitterActive: false

actions:
toggleVote: ->
if @get('vote')
vote = @get('vote')
vote.deleteRecord()
else
vote = @store.createRecord('vote',
proposal: @get('model')
)

vote.save().then(=>
@toggleProperty('selected')
).catch((error) ->
alert(error.responseText)
)

false

hide: ->
@set('model.visible', false)
false

showAbstract: ->
@send('toggleOff')
@set('abstractActive', true)
false
showNotes: ->
@send('toggleOff')
@set('notesActive', true)
false
showPitch: ->
@send('toggleOff')
@set('pitchActive', true)
false
showSubmitter: ->
@send('toggleOff')
@set('submitterActive', true)
false
toggleOff: ->
@set('abstractActive', false)
@set('notesActive', false)
@set('pitchActive', false)
@set('submitterActive', false)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Proposals.VotingRoundController = Ember.ObjectController.extend
visibleProposals: (->
@get('proposals').filterBy('visible').sortBy('title')
).property('[email protected]')
11 changes: 11 additions & 0 deletions app/assets/javascripts/ember-app.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#= require jquery
#= require jquery_ujs
#= require showdown
#= require handlebars
#= require ember
#= require ember-data
#= require_self
#= require proposals

window.Proposals = Ember.Application.create
rootElement: '#app'
Empty file.
Empty file.
Empty file.
11 changes: 11 additions & 0 deletions app/assets/javascripts/models/proposal.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Proposals.Proposal = DS.Model.extend
title: DS.attr('string')
abstract: DS.attr('string')
notes: DS.attr('string')
pitch: DS.attr('string')
userName: DS.attr('string')
twitter: DS.attr('string')
github: DS.attr('string')
selected: DS.attr('boolean')
vote: DS.belongsTo('vote')
visible: true
6 changes: 6 additions & 0 deletions app/assets/javascripts/models/round.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Proposals.Round = DS.Model.extend
text: DS.attr('string')
totalVotes: DS.attr('number')
isAnonymous: DS.attr('boolean')
isCurrentRound: DS.attr('boolean')
name: DS.attr('string')
5 changes: 5 additions & 0 deletions app/assets/javascripts/models/user.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Proposals.User = DS.Model.extend
name: DS.attr('string')
email: DS.attr('string')
isAdmin: DS.attr('boolean')
isVoter: DS.attr('boolean')
3 changes: 3 additions & 0 deletions app/assets/javascripts/models/vote.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Proposals.Vote = DS.Model.extend
proposal: DS.belongsTo('proposal')
round: DS.attr('string')
42 changes: 42 additions & 0 deletions app/assets/javascripts/proposals.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#= require ./store
#= require_tree ./models
#= require_tree ./controllers
#= require_tree ./views
#= require_tree ./helpers
#= require_tree ./components
#= require_tree ./templates
#= require_tree ./routes
#= require ./router
#= require_self

$ ->
$.ajaxPrefilter (options, originalOptions, xhr) ->
token = $('meta[name="csrf-token"]').attr('content')
xhr.setRequestHeader('X-CSRF-Token', token)

# Create an intializer that injects Rails' current_user into the Ember app.
# http://say26.com/using-rails-devise-with-ember-js
Ember.Application.initializer
name: 'currentUser'

initialize: (container) ->
store = container.lookup('store:main')
attributes = $('meta[name="current-user"]').attr('content')

if attributes
# Manually normalize attributes (underscore to camelcase) of serialized
# user and push into the store (creating an instance)
user = store.push('user', store.serializerFor('user').normalize(Proposals.User, JSON.parse(attributes)))

# Put the user instance into the content variable of CurrentUserController
controller = container.lookup('controller:currentUser').set('content', user)

# Perform typeInjection on all the controllers in our app to give them
# currentUser variable so that we don't need to set it manually.
container.typeInjection('controller', 'currentUser', 'controller:currentUser')

showdown = new Showdown.converter()

Ember.Handlebars.helper('format-markdown', (input) ->
new Handlebars.SafeString(showdown.makeHtml(input))
)
7 changes: 7 additions & 0 deletions app/assets/javascripts/router.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Proposals.Router.map ()->
@resource('voting_round', path: '/round/:round')

Proposals.Router.reopen
rootURL: '/voting/'
location: 'history'

Empty file.
4 changes: 4 additions & 0 deletions app/assets/javascripts/routes/index_route.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Proposals.IndexRoute = Ember.Route.extend
setupController: (controller, model) ->
controller.set('rounds', @store.find('round'))
controller.set('votes', @store.find('vote'))
7 changes: 7 additions & 0 deletions app/assets/javascripts/routes/voting_round_route.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Proposals.VotingRoundRoute = Ember.Route.extend
model: (params) ->
@store.find('round', params.round)

setupController: (controller, model) ->
controller.set('model', model)
controller.set('proposals', @store.find('proposal', {round: model.name}))
4 changes: 4 additions & 0 deletions app/assets/javascripts/store.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Proposals.Store = DS.Store.extend({})

Proposals.ApplicationAdapter = DS.ActiveModelAdapter.extend
namespace: 'api'
Empty file.
36 changes: 36 additions & 0 deletions app/assets/javascripts/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<nav class="top-bar" data-topbar>
<ul class="title-area">
<li class="name">
<h1><a href="/">Burlington Ruby CFP</a></h1>
</li>
</ul>

<section class="top-bar-section">
<ul class="right">
<li>{{link-to "<i class='fi-clipboard-pencil'></i> Voting" 'index'}}</li>
<li><a href="/logout">Sign Out</a></li>
</ul>
</section>
</nav>

{{outlet}}

<footer class="row">
<div class="large-12 columns">
<hr />
<div class="row">
<div class="medium-6 large-4 columns">
<p>©2014 <a href='http://burlingtonrubyconference.com/'>Burlington Ruby Conference</a></p>
</div>
<div class="medium-6 large-8 columns">
<ul class="inline-list right">
<li><a href='http://burlingtonrubyconference.com/conduct.html'><i class='fi-list-thumbnails'></i> Code of Conduct</a></li>
<li><a href='http://burlingtonrubyconference.com/diversity.html'><i class='fi-male-female'></i> Diversity Statement</a></li>
<li><a href='https://github.com/burlingtonruby/proposals'><i class='fi-social-github'></i> GitHub</a></li>
<li><a href='https://twitter.com/btvrubyconf'><i class='fi-social-twitter'></i> Twitter</a></li>
</ul>
</div>
</div>
</div>
</footer>

Empty file.
Empty file.
24 changes: 24 additions & 0 deletions app/assets/javascripts/templates/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div class="row voting">
{{#with view as VotingView}}
{{#each round in VotingView.controller.rounds}}
<div class="small-12 medium-6 columns">
<h2>{{round.text}}</h2>

{{#if round.isCurrentRound}}
<p>Voting is now open for {{round.text}}. {{link-to "Cast your vote." "voting_round" round.id}}</p>
{{else}}
<p>Voting is closed for {{round.text}}.</p>
{{/if}}

<h3>Your votes for {{round.text}}</h3>
<ol>
{{#view Proposals.CurrentVotesView controller=VotingView.controller round=round}}
{{#each view.currentVotes}}
<li>{{proposal.title}}</li>
{{/each}}
{{/view}}
</ol>
</div>
{{/each}}
{{/with}}
</div>
Loading

0 comments on commit a304ce6

Please sign in to comment.