Skip to content

Commit

Permalink
Merge branch 'parties-example' into release-0.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
glasser committed Oct 16, 2012
2 parents bf656fb + 15c6fe4 commit 7b03dfc
Show file tree
Hide file tree
Showing 8 changed files with 747 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/parties/.meteor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
local
13 changes: 13 additions & 0 deletions examples/parties/.meteor/packages
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Meteor packages used by this project, one per line.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.

preserve-inputs
accounts-ui
accounts-password
d3
bootstrap
email
accounts-facebook
accounts-twitter
276 changes: 276 additions & 0 deletions examples/parties/client/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// All Tomorrow's Parties -- client

Meteor.subscribe("directory");
Meteor.subscribe("parties");

// If no party selected, select one.
Meteor.startup(function () {
Meteor.autorun(function () {
if (! Session.get("selected")) {
var party = Parties.findOne();
if (party)
Session.set("selected", party._id);
}
});
});

///////////////////////////////////////////////////////////////////////////////
// Party details sidebar

Template.details.party = function () {
return Parties.findOne(Session.get("selected"));
};

Template.details.anyParties = function () {
return Parties.find().count() > 0;
};

Template.details.creatorName = function () {
var owner = Meteor.users.findOne(this.owner);
if (owner._id === Meteor.userId())
return "me";
return displayName(owner);
};

Template.details.canRemove = function () {
return this.owner === Meteor.userId() && attending(this) === 0;
};

Template.details.maybeChosen = function (what) {
var myRsvp = _.find(this.rsvps, function (r) {
return r.user === Meteor.userId();
}) || {};

return what == myRsvp.rsvp ? "chosen btn-inverse" : "";
};

Template.details.events({
'click .rsvp_yes': function () {
Meteor.call("rsvp", Session.get("selected"), "yes");
return false;
},
'click .rsvp_maybe': function () {
Meteor.call("rsvp", Session.get("selected"), "maybe");
return false;
},
'click .rsvp_no': function () {
Meteor.call("rsvp", Session.get("selected"), "no");
return false;
},
'click .invite': function () {
openInviteDialog();
return false;
},
'click .remove': function () {
Parties.remove(this._id);
return false;
}
});

///////////////////////////////////////////////////////////////////////////////
// Party attendance widget

Template.attendance.rsvpName = function () {
var user = Meteor.users.findOne(this.user);
return displayName(user);
};

Template.attendance.outstandingInvitations = function () {
var party = Parties.findOne(this._id);
return Meteor.users.find({$and: [
{_id: {$in: party.invited}}, // they're invited
{_id: {$nin: _.pluck(party.rsvps, 'user')}} // but haven't RSVP'd
]});
};

Template.attendance.invitationName = function () {
return displayName(this);
};

Template.attendance.rsvpIs = function (what) {
return this.rsvp === what;
};

Template.attendance.nobody = function () {
return ! this.public && (this.rsvps.length + this.invited.length === 0);
};

Template.attendance.canInvite = function () {
return ! this.public && this.owner === Meteor.userId();
};

///////////////////////////////////////////////////////////////////////////////
// Map display

// Use jquery to get the position clicked relative to the map element.
var coordsRelativeToElement = function (element, event) {
var offset = $(element).offset();
var x = event.pageX - offset.left;
var y = event.pageY - offset.top;
return { x: x, y: y };
};

Template.map.events = {
'mousedown circle, mousedown text': function (event, template) {
Session.set("selected", event.currentTarget.id);
},
'dblclick svg': function (event, template) {
if (! Meteor.userId()) // must be logged in to create events
return;
var coords = coordsRelativeToElement(event.currentTarget, event);
openCreateDialog(coords.x / 500, coords.y / 500);
}
};

Template.map.rendered = function () {
var self = this;
self.node = self.find("svg");

if (! self.handle) {
self.handle = Meteor.autorun(function () {
var selected = Session.get('selected');
var selectedParty = selected && Parties.findOne(selected);
var radius = function (party) {
return 10 + Math.sqrt(attending(party)) * 10;
};

// Draw a circle for each party
var updateCircles = function (group) {
group.attr("id", function (party) { return party._id; })
.attr("cx", function (party) { return party.x * 500; })
.attr("cy", function (party) { return party.y * 500; })
.attr("r", radius)
.attr("class", function (party) {
return party.public ? "public" : "private";
})
.style('opacity', function (party) {
return selected === party._id ? 1 : 0.6;
});
};

var circles = d3.select(self.node).select(".circles").selectAll("circle")
.data(Parties.find().fetch(), function (party) { return party._id; });

updateCircles(circles.enter().append("circle"));
updateCircles(circles.transition().duration(250).ease("cubic-out"));
circles.exit().transition().duration(250).attr("r", 0).remove();

// Label each with the current attendance count
var updateLabels = function (group) {
group.attr("id", function (party) { return party._id; })
.text(function (party) {return attending(party) || '';})
.attr("x", function (party) { return party.x * 500; })
.attr("y", function (party) { return party.y * 500 + radius(party)/2 })
.style('font-size', function (party) {
return radius(party) * 1.25 + "px";
});
};

var labels = d3.select(self.node).select(".labels").selectAll("text")
.data(Parties.find().fetch(), function (party) { return party._id; });

updateLabels(labels.enter().append("text"));
updateLabels(labels.transition().duration(250).ease("cubic-out"));
labels.exit().remove();

// Draw a dashed circle around the currently selected party, if any
var callout = d3.select(self.node).select("circle.callout")
.transition().duration(250).ease("cubic-out");
if (selectedParty)
callout.attr("cx", selectedParty.x * 500)
.attr("cy", selectedParty.y * 500)
.attr("r", radius(selectedParty) + 10)
.attr("class", "callout")
.attr("display", '');
else
callout.attr("display", 'none');
});
}
};

Template.map.destroyed = function () {
this.handle && this.handle.stop();
};

///////////////////////////////////////////////////////////////////////////////
// Create Party dialog

var openCreateDialog = function (x, y) {
Session.set("createCoords", {x: x, y: y});
Session.set("createError", null);
Session.set("showCreateDialog", true);
};

Template.page.showCreateDialog = function () {
return Session.get("showCreateDialog");
};

Template.createDialog.events = {
'click .save': function (event, template) {
var title = template.find(".title").value;
var description = template.find(".description").value;
var public = ! template.find(".private").checked;
var coords = Session.get("createCoords");

if (title.length && description.length) {
Meteor.call('createParty', {
title: title,
description: description,
x: coords.x,
y: coords.y,
public: public
}, function (error, party) {
if (! error) {
Session.set("selected", party);
if (! public && Meteor.users.find().count() > 1)
openInviteDialog();
}
});
Session.set("showCreateDialog", false);
} else {
Session.set("createError",
"It needs a title and a description, or why bother?");
}
},

'click .cancel': function () {
Session.set("showCreateDialog", false);
}
};

Template.createDialog.error = function () {
return Session.get("createError");
};

///////////////////////////////////////////////////////////////////////////////
// Invite dialog

var openInviteDialog = function () {
Session.set("showInviteDialog", true);
};

Template.page.showInviteDialog = function () {
return Session.get("showInviteDialog");
};

Template.inviteDialog.events = {
'click .invite': function (event, template) {
Meteor.call('invite', Session.get("selected"), this._id);
},
'click .done': function (event, template) {
Session.set("showInviteDialog", false);
return false;
}
};

Template.inviteDialog.uninvited = function () {
var party = Parties.findOne(Session.get("selected"));
if (! party)
return []; // party hasn't loaded yet
return Meteor.users.find({$nor: [{_id: {$in: party.invited}},
{_id: party.owner}]});
};

Template.inviteDialog.displayName = function () {
return displayName(this);
};
72 changes: 72 additions & 0 deletions examples/parties/client/parties.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.header {
padding: 20px 0;
}

.details {
margin-top: -18px;
}

.mask {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
background-color: #000000;
opacity: .4;
z-index: 1;
}

.invite-row .invite {
margin: 10px 10px 10px 0;
}

.rsvp-buttons {
text-align: center;
margin: 40px 0 40px 0;
}

.description {
margin: 20px 0 20px 0;
}

.attendance .who {
margin-bottom: 5px;
}

.attendance .invite {
text-align: center;
}

input.chosen {
font-weight: bold;
}

.map {
background-image: url('/soma.jpeg');
background-position: -20px -20px;
width: 500px;
height: 500px;
}

.map circle.public {
fill: #49AFCD;
}

.map circle.private {
fill: #DA4F49;
}

.map text {
text-anchor: middle;
fill: white;
font-weight: bold;
}

.map circle.callout {
stroke-width: 5px;
stroke-dasharray: 9, 5;
stroke-opacity: .8;
fill: none;
stroke: red;
}
Loading

0 comments on commit 7b03dfc

Please sign in to comment.