Permalink
Browse files

Added in the latest and greatest Rafflr. Created a jquery-ui compatib…

…le Rafflr widget. Using d3 to drive SVG manipulation and animations. Loser transitions are organized in a plugin-type system under app/assets/javascripts/rafflr/ so new animations can be added in via a standardized way with no extra coding.
  • Loading branch information...
1 parent 1e4cbd7 commit be08502b0518e61e2e916c1ece19beebb1eddbb8 Ethan Waldo committed Mar 14, 2012
@@ -1,6 +1,7 @@
//= require jquery
//= require jquery_ujs
//= require jquery-ui
+//= require underscore
//= require jquery.mobile
//= require jquery.mobile.datebox
//= require jquery.mobile.pagination
@@ -0,0 +1,205 @@
+//= require_self
+//= require_tree ./rafflr
+
+(function($) {
+ $.widget( "raor.rafflr", {
+ data: null,
+ svg: null,
+ topPadding: 20,
+ textHeight: 30,
+ textMargin: 5,
+ transitionKeys: [],
+ transitions: {},
+ winners: 3,
+ url: null,
+ _create: function() {
+ $.extend(true, this, this.options);
+ $.Widget.prototype._create.apply(this, arguments);
+ },
+ _init: function() {
+ var self = this;
+ $.Widget.prototype._init.apply(self, arguments);
+
+ for(var key in self.transitions){
+ self.transitionKeys.push(key);
+ }
+ },
+ _start: function(data, textStatus, jqXHR) {
+ var self = this;
+ self.data = $.map(data, function(value, indexOrKey) {
+ return {id: value.user_id, name: value.user.name};
+ });
+
+ // Uncomment to add more names to the data
+// self.data = self.data.concat([
+// {id: 2, name: "Bob Jones"},
+// {id: 3, name: "Nancy Reed"},
+// {id: 4, name: "Larry David"},
+// {id: 5, name: "George Foreman"},
+// {id: 6, name: "Barry Lither"},
+// {id: 7, name: "Cary Grant"},
+// {id: 8, name: "Penelope Cruz"},
+// {id: 9, name: "Steven Segal"},
+// {id: 10, name: "Rasputin"}
+// ]);
+
+ var divElem = d3.select(".page");
+
+ self.h = Math.round($(".page:visible").innerHeight() - $(".header").outerHeight(true) - $(".page:visible > .title").outerHeight(true) - $(".footer").outerHeight(true));
+ self.w = $(".page:visible").innerWidth();
+
+ // Create svg tag before div.footer
+ self.svg = divElem.insert("svg", ".footer")
+ .attr("xmlns", "http://www.w3.org/2000/svg")
+ .attr("height", this.h + "px")
+ .attr("width", this.w + "px");
+
+ // Force jQuery mobile to reset header and footer
+ $("body").trigger("updatelayout");
+
+ // Used to group the rect and text for each name for transforms
+ var groups = self.svg.selectAll("g")
+ .data(self.data, function(d) { return d.id; })
+ .enter()
+ .append("g");
+
+ // Create text vertically centered
+ var text = groups.append("text")
+ .attr("class", "Label")
+ .attr("dominant-baseline", "middle")
+ .attr("x", 0)
+ .attr("y", self.textHeight / 2)
+ .attr("height", function(d, i) {
+ return self.textHeight;
+ });
+
+ // Put text in tspan with slight horizontal padding
+ text.append("tspan")
+ .attr("x", self.textMargin)
+ .text(function(d, i) {
+ return d.name;
+ });
+
+ // Set bounding box property so we know what the text size is
+ text.each(function(d, i) {
+ d.bbox = this.getBBox();
+ });
+
+ // Have to create rects after text because text can't be measured until render
+ // Rects need to be inserted before text so that text is on top of rect
+ var rects = groups.insert("rect", "text")
+ .attr("class", "name")
+ .attr("x", function(d, i) {
+ d.x = 0;
+ return d.x;
+ })
+ .attr("y", function(d, i) {
+ d.y = 0;
+ return d.y;
+ })
+ .attr("width", function(d, i) {
+ d.width = d.bbox.width + (self.textMargin * 2);
+ return d.width;
+ })
+ .attr("height", function(d, i) {
+ d.height = self.textHeight;
+ return d.height;
+ });
+
+ // Now that we have valid text sizes, tile up the groups
+ var row = 0;
+
+ var filtered = groups.filter(function(d, i) {
+ return i > 0;
+ });
+ var count = filtered[0].length;
+
+ filtered.transition()
+ .duration(5000)
+ .attr("transform", function(d, i) {
+ var prev = d3.select(groups[0][i]);
+ var x = prev.data()[0].x;
+ var y = prev.data()[0].y;
+ var height = prev.data()[0].height;
+ var width = prev.data()[0].width;
+ d.x = x + width;
+ d.y = y;
+ if(d.x + d.width > self.w) {
+ row += 1;
+ d.x = 0;
+ d.y = y + height;
+ }
+ return "translate(" + d.x + "," + d.y + ")";
+ })
+ .each("end", function(d, i) {
+ count -= 1;
+ if(count == 0) {
+ $.proxy(self._bootLosers, self)();
+ }
+ });
+ },
+ _bootLosers: function() {
+ var self = this;
+ var selection = self.svg.selectAll("g");
+ // Set bounding box property so we know what the text size is
+
+ self.svg.selectAll("g").each(function(d, i) {
+ d.bbox = this.getBBox();
+ });
+
+ self.data.splice(Math.floor(Math.random() * selection[0].length), 1);
+ selection.data(self.data, function(d) { return d.id; })
+ .exit()
+ .transition()
+ .call($.proxy(self._pickTransition, self))
+ .remove()
+ .each("end", function(d, i) {
+ if(self.data.length > self.winners) {
+ $.proxy(self._bootLosers, self)();
+ } else {
+ $.proxy(self._selectWinners, self)();
+ }
+ });
+ },
+ _selectWinners: function() {
+ var self = this;
+ var selection = self.svg.selectAll("g");
+
+ var maxWidth = _.max($.map(selection.data(), function(value, indexOrKey) {
+ return value.width;
+ }));
+
+ var maxHeight = _.max($.map(selection.data(), function(value, indexOrKey) {
+ return value.height;
+ }));
+
+ var scale = 2;
+
+ self.data = _.shuffle(self.data);
+ selection.sort(function(a, b) {
+ var aIndex = self.data.indexOf(a);
+ var bIndex = self.data.indexOf(b);
+ return aIndex > bIndex ? -1 : (aIndex == bIndex ? 0 : 1);
+ })
+ .transition()
+ .delay(3000)
+ .duration(5000)
+ .attr("transform", function(d, i) {
+ var x = (self.w / 2) - (d.width * scale / 2);
+ var yMargin = (self.h - (d.height * scale * selection[0].length)) / 2;
+ var y = yMargin + (d.height * scale * i);
+ return "translate(" + x + " " + y + ") scale(" + scale + ")"
+ });
+ },
+ _pickTransition: function(transition) {
+ var self = this;
+
+ var pick = Math.floor(Math.random() * self.transitionKeys.length);
+ self.transitions[self.transitionKeys[pick]].call(self, transition);
+ },
+ start: function() {
+ var self = this;
+ $.getJSON(self.url, $.proxy(self._start, self));
+ }
+ });
+}(jQuery));
@@ -0,0 +1,6 @@
+(function($) {
+ $.raor.rafflr.prototype.transitions.fadeout = function(transition) {
+ transition.duration(5000)
+ .style("opacity", 0);
+ }
+}(jQuery));
@@ -0,0 +1,21 @@
+(function($) {
+ $.raor.rafflr.prototype.transitions.rotate = function(transition) {
+ var r = 0;
+
+ function rotate(d, i) {
+ r += 180;
+ var x = d.x + (d.bbox.width / 2);
+ var y = (d.y + (d.bbox.height / 2));
+ return "rotate(" + r + " " + x + " " + y + ") translate(" + d.x + " " + d.y + ")";
+ }
+
+ var transition2 = transition.transition();
+
+ transition.delay(2500)
+ .duration(2500)
+ .attr("transform", rotate);
+
+ transition2.duration(2500)
+ .attr("transform", rotate);
+ }
+}(jQuery));
@@ -0,0 +1,9 @@
+(function($) {
+ $.raor.rafflr.prototype.transitions.translate = function(transition) {
+ var self = this;
+ transition.duration(5000)
+ .attr("transform", function(d, i) {
+ return "translate(" + ((self.w / 2) - (d.width / 2)) + " " + ((self.h / 2) - (d.height / 2)) + ")";
+ });
+ }
+}(jQuery));
@@ -2,6 +2,7 @@
*= require jquery.mobile
*= require jquery.mobile.pagination
*= require jquery.mobile.datebox
+ *= require rafflr
*/
span.title {
@@ -0,0 +1,23 @@
+.title {
+ text-align: center;
+ span {
+ font-family: "Verdana";
+ font-size: 200%;
+ font-weight: bold;
+ }
+}
+
+.controls {
+ float: right;
+}
+
+rect {
+ stroke: white;
+ fill: steelblue;
+}
+
+text {
+ font-family: "Verdana";
+ font-size: "55";
+ fill: "white";
+}
@@ -2,7 +2,7 @@ class CheckinsController < ApplicationController
load_and_authorize_resource :event
load_and_authorize_resource :checkin, :through => :event
- respond_to :html
+ respond_to :html, :json
def index
case as_what?
@@ -11,7 +11,13 @@ def index
@checkins = Checkin.unhidden
end
- respond_with(@event, @checkins)
+ respond_with(@event, @checkins) do |format|
+ format.html
+
+ format.json do
+ render :json => @checkins, :include => :user
+ end
+ end
end
def show
@@ -98,13 +98,19 @@ def carousel
@checkins = @event.checkins.order(:created_at).limit(4)
end
respond_with(@event) do |format|
- format.html do
- render
- end
+ format.html
format.json do
render :json => @checkins.to_json(:only => [:id, :employer, :employ, :employment, :shoutout], :include => [:user => {:only => [:name]}])
end
end
end
+
+ def rafflr
+ @event = Event.find(params[:event_id])
+ @checkins = @event.checkins
+ respond_with(@event) do |format|
+ format.html
+ end
+ end
end
@@ -0,0 +1,25 @@
+$(document).ready(function() {
+ var started = false;
+
+ $("button.start").click(function() {
+ if(started) {
+ location.reload(true);
+ } else {
+ var winners = parseInt($(".winners").val());
+
+ if(winners && winners > 0) {
+ started = true;
+ var rafflr = $("body").rafflr({
+ url: "<%= event_checkins_path(@event, :format => :json) %>",
+ winners: winners
+ });
+
+ $(this).html("Stop");
+
+ rafflr.data("rafflr").start();
+ } else {
+ alert("Please enter a valid number of winners.");
+ }
+ }
+ })
+});
@@ -0,0 +1,14 @@
+- content_for :javascript do
+ = javascript_include_tag "d3.v2", "rafflr"
+ script type="text/javascript"
+ - with_format('js') do
+ = render :partial => "rafflr"
+.title
+ span
+ | Rafflr 2.0
+ .controls
+ label for="winners"
+ | Winners:
+ input.winners type="number" name="winners" size="3" value="3" data-role="none"
+ button.start data-inline="true" data-role="none"
+ | Start
@@ -13,6 +13,8 @@ p
p
= link_to "Checkin Carousel", event_carousel_path(@event), :method => :get, :"data-role" => "button", :"data-inline" => "true", :target => "_self"
+ = link_to "Rafflr", event_rafflr_path(@event), :method => :get, :"data-role" => "button", :"data-inline" => "true", :target => "_self"
+p
- if can? :update, @event
= link_to "Edit", edit_event_path(@event), :method => :get, :"data-role" => "button", :"data-inline" => "true"
= link_to "Delete", event_path(@event), :method => :delete, :"data-role" => "button", :"data-inline" => "true"
@@ -15,7 +15,7 @@ html
- back = back_path
= link_to "Back", back, :method => :get, :class => "back ui-btn-left", :"data-inline" => true, :"data-role" => "button" unless back.blank?
h1
- | RAOR
+ = link_to "RAOR", root_path, :"data-inline" => true, :"data-role" => "button", :target => "_self"
= link_to "Logout", destroy_user_session_path, :method => :delete, :class => "ui-btn-right", :"data-inline" => true, :"data-role" => "button", :target => "_self"
- if notice
= link_to notice, "#", :id => "notice", :"data-role" => "button", :"data-icon" => "delete", :"data-iconpos" => "right"
Oops, something went wrong.

0 comments on commit be08502

Please sign in to comment.