<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[919] sites/trunk/wordcamp.org/public_html/wp-content/plugins: WordCamp.org Plugins: Add the new CampTix Attendance addon.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/919">919</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/919","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>kovshenin</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2014-10-21 15:23:09 +0000 (Tue, 21 Oct 2014)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>WordCamp.org Plugins: Add the new CampTix Attendance addon.</pre>
<h3>Added Paths</h3>
<ul>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/</li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/</li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsassetsattendanceuicss">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.css</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsassetsattendanceuijs">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.js</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsassetsattendanceuiscss">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.scss</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsassetsjqueryfastbuttonjs">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/jquery.fastbutton.js</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsattendanceuiphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/attendance-ui.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsattendancephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/attendance.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendancecamptixattendancephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/camptix-attendance.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsassetsattendanceuicss"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.css</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.css (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.css 2014-10-21 15:23:09 UTC (rev 919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,364 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,700);
+body {
+ background: #ccc;
+ font-family: 'Open Sans', Helvetica, sans-serif; }
+
+.overlay {
+ display: none;
+ position: absolute;
+ background: black;
+ opacity: 0.8;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 200; }
+
+header {
+ display: block;
+ position: absolute;
+ background: #333;
+ height: 50px;
+ top: 0;
+ left: 0;
+ right: 0;
+ color: white;
+ padding-left: 16px;
+ z-index: 100; }
+
+header h1,
+.attendee-filter-view h1 {
+ display: block;
+ clear: none;
+ line-height: 50px;
+ margin: 0;
+ font-size: 16px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-right: 100px;
+ color: white; }
+
+.menu {
+ float: right;
+ height: 50px; }
+ .menu .dashicons {
+ color: white;
+ float: left;
+ width: 50px;
+ height: 50px;
+ font-size: 28px;
+ line-height: 50px;
+ text-align: center; }
+
+.loading {
+ line-height: 50px;
+ color: #999; }
+ .loading .spinner-container {
+ width: 50px;
+ height: 50px;
+ float: left;
+ overflow: hidden;
+ text-align: center; }
+ .loading .spinner {
+ display: inline-block;
+ margin-top: 14px;
+ border-top: 5px solid rgba(200, 200, 200, 0.3);
+ border-right: 5px solid rgba(200, 200, 200, 0.3);
+ border-bottom: 5px solid rgba(200, 200, 200, 0.3);
+ border-left: 5px solid #ccc;
+ -webkit-animation: load8 1.1s infinite linear;
+ animation: load8 1.1s infinite linear; }
+ .loading .spinner,
+ .loading .spinner:after {
+ border-radius: 50%;
+ width: 12px;
+ height: 12px; }
+
+li.item .spinner-container {
+ display: none;
+ width: 50px;
+ height: 50px;
+ background: white;
+ overflow: hidden;
+ position: absolute;
+ left: 0; }
+li.item.camptix-loading .spinner-container {
+ display: block; }
+li.item .spinner,
+li.item .spinner:before,
+li.item .spinner:after {
+ display: block;
+ background: #ccc;
+ -webkit-animation: load1 1s infinite ease-in-out;
+ animation: load1 1s infinite ease-in-out;
+ width: 5px;
+ height: 10px; }
+li.item .spinner:before,
+li.item .spinner:after {
+ position: absolute;
+ top: 0;
+ content: ''; }
+li.item .spinner:before {
+ left: 7px; }
+li.item .spinner {
+ margin-left: 22px;
+ margin-top: 20px;
+ position: relative;
+ font-size: 11px;
+ -webkit-animation-delay: -0.16s;
+ animation-delay: -0.16s; }
+li.item .spinner:after {
+ left: -7px;
+ -webkit-animation-delay: -0.32s;
+ animation-delay: -0.32s; }
+
+.menu .submenu {
+ display: none;
+ position: absolute;
+ top: 50px;
+ right: 0;
+ background: #444;
+ min-height: 50px; }
+ .menu .submenu a {
+ height: 51px;
+ line-height: 50px;
+ text-decoration: none;
+ color: white;
+ padding: 0 20px;
+ display: block;
+ min-width: 150px;
+ max-width: 200px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis; }
+
+.menu.dropdown .dashicons-menu {
+ background: #444; }
+
+.menu.dropdown .submenu {
+ display: block; }
+
+.attendee-search-view,
+.attendee-filter-view {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ height: 50px;
+ background: #444;
+ color: white; }
+ .attendee-search-view .wrapper:before,
+ .attendee-filter-view .wrapper:before {
+ content: "\f179";
+ font-family: 'dashicons';
+ -webkit-font-smoothing: antialiased;
+ font-size: 28px;
+ height: 50px;
+ width: 50px;
+ left: 0;
+ position: absolute;
+ line-height: 50px;
+ text-align: center; }
+ .attendee-search-view .close,
+ .attendee-filter-view .close {
+ float: right;
+ color: white;
+ width: 50px;
+ height: 50px;
+ font-size: 28px;
+ line-height: 50px;
+ text-align: center; }
+
+.attendee-search-view .wrapper {
+ height: 50px;
+ display: block;
+ margin-right: 50px;
+ margin-left: 50px; }
+
+.attendee-search-view input {
+ border: 0;
+ font-size: 16px;
+ font-family: 'Open Sans', Helvetica, sans-serif;
+ height: 35px;
+ margin-top: 7px;
+ padding: 0 8px;
+ width: 100%;
+ margin-left: -4px; }
+
+.attendee-filter-view {
+ z-index: 500;
+ height: auto;
+ bottom: 0;
+ overflow: scroll; }
+ .attendee-filter-view .wrapper:before {
+ content: "\f180"; }
+ .attendee-filter-view h1 {
+ padding-left: 50px; }
+ .attendee-filter-view h1.section-title {
+ padding-left: 18px;
+ border-bottom: solid 1px #555;
+ margin: 0; }
+ .attendee-filter-view ul.section-controls {
+ list-style: none;
+ margin: 0;
+ padding: 0; }
+ .attendee-filter-view ul.section-controls li {
+ height: 50px;
+ line-height: 50px;
+ margin: 0;
+ border-bottom: solid 1px #555;
+ padding-left: 50px;
+ padding-right: 50px;
+ cursor: pointer;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis; }
+ .attendee-filter-view ul.section-controls li.selected:before {
+ content: "\f147";
+ font-family: 'dashicons';
+ -webkit-font-smoothing: antialiased;
+ font-size: 28px;
+ height: 50px;
+ width: 50px;
+ left: 0;
+ position: absolute;
+ line-height: 50px;
+ text-align: center; }
+
+.attendees-list {
+ margin: 0;
+ padding: 0;
+ background: white;
+ position: absolute;
+ top: 50px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch; }
+ .attendees-list li {
+ display: block;
+ float: left;
+ width: 100%;
+ height: 50px;
+ border-bottom: solid 1px #ddd;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
+ .attendees-list li.item {
+ cursor: pointer; }
+ .attendees-list .toggle {
+ display: block;
+ float: left;
+ width: 50px;
+ height: 50px;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
+ .attendees-list .toggle .dashicons {
+ font-size: 24px;
+ line-height: 50px;
+ height: 50px;
+ width: 50px;
+ text-align: center;
+ text-decoration: none;
+ color: #ddd; }
+ .attendees-list .toggle.yes .dashicons {
+ color: #0acc00; }
+ .attendees-list .name {
+ display: block;
+ margin-left: 50px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ height: 50px;
+ line-height: 50px;
+ font-size: 16px;
+ padding-left: 2px;
+ padding-right: 16px; }
+
+.attendee-toggle-wrap {
+ margin: 0;
+ padding: 0;
+ background: white;
+ position: absolute;
+ top: 20px;
+ bottom: 20px;
+ left: 20px;
+ right: 20px;
+ overflow: scroll;
+ padding: 50px 40px;
+ text-align: center;
+ z-index: 300; }
+ .attendee-toggle-wrap .yes-no-container {
+ height: 50px;
+ display: block;
+ width: 200px;
+ margin: 40px auto 0 auto; }
+ .attendee-toggle-wrap a.yes,
+ .attendee-toggle-wrap a.no {
+ height: 50px;
+ display: block;
+ float: left;
+ width: 50%;
+ line-height: 50px;
+ font-size: 16px;
+ background: #333;
+ color: white;
+ text-decoration: none; }
+ .attendee-toggle-wrap a.yes {
+ background: #0acc00; }
+ .attendee-toggle-wrap .close {
+ font-size: 24px;
+ line-height: 50px;
+ height: 50px;
+ width: 50px;
+ text-align: center;
+ text-decoration: none;
+ color: #444;
+ position: absolute;
+ top: 0;
+ right: 0; }
+ .attendee-toggle-wrap img {
+ background: #ccc;
+ width: 150px;
+ height: 150px; }
+
+/** Animations, courtesy of https://github.com/lukehaas/css-loaders **/
+@-webkit-keyframes load1 {
+ 0%,
+ 80%,
+ 100% {
+ box-shadow: 0 0 #ccc;
+ height: 10px; }
+
+ 40% {
+ box-shadow: 0 -5px #ccc;
+ height: 18px; } }
+
+@keyframes load1 {
+ 0%,
+ 80%,
+ 100% {
+ box-shadow: 0 0 #ccc;
+ height: 10px; }
+
+ 40% {
+ box-shadow: 0 -5px #ccc;
+ height: 18px; } }
+
+@-webkit-keyframes load8 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg); }
+
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg); } }
+
+@keyframes load8 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg); }
+
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg); } }
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsassetsattendanceuijs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.js (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.js 2014-10-21 15:23:09 UTC (rev 919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,591 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+var camptix = camptix || {};
+
+jQuery(document).ready(function($){
+
+ camptix.models = camptix.models || {};
+ camptix.views = camptix.views || {};
+ camptix.collections = camptix.collections || {};
+
+ /**
+ * Attendee Model
+ *
+ * This model represents an attendee and their attendance status.
+ */
+ camptix.models.Attendee = Backbone.Model.extend({
+ defaults: function() {
+ return {
+ status: false,
+ avatar: '',
+ name: '',
+ }
+ },
+
+ /**
+ * Set the attendance status and save on server.
+ */
+ toggle: function( attended ) {
+ this.save({ status: attended });
+ },
+
+ /**
+ * Sync attendance status with the server.
+ */
+ sync: function( method, model, options ) {
+ var model = this;
+ model.trigger( 'camptix:sync:start' );
+
+ options = options || {};
+ options.context = this;
+ options.type = 'GET';
+
+ options.data = _.extend( options.data || {}, {
+ action: 'camptix-attendance',
+ camptix_secret: _camptixAttendanceSecret
+ });
+
+ if ( method == 'read' ) {
+ options.data = _.extend( options.data || {}, {
+ camptix_action: 'sync-model',
+ camptix_id: this.id
+ });
+
+ return wp.ajax.send( options ).done( function() { model.trigger( 'camptix:sync:end' ); } );
+
+ } else if ( method == 'update' ) {
+ options.data = _.extend( options.data || {}, {
+ camptix_action: 'sync-model',
+ camptix_set_attendance: this.get( 'status' ),
+ camptix_id: this.id
+ });
+
+ return wp.ajax.send( options ).done( function() { model.trigger( 'camptix:sync:end' ) } );
+ }
+ }
+ });
+
+ /**
+ * Attendees Collection
+ *
+ * A collection to query and hold lists of attendees.
+ */
+ camptix.collections.AttendeesList = Backbone.Collection.extend({
+
+ model: camptix.models.Attendee,
+
+ initialize: function( models, options ) {
+ this._hasMore = true;
+ this.query = options.query;
+ this.controller = options.controller;
+ },
+
+ /**
+ * Talk to the server for more items.
+ */
+ sync: function( method, model, options ) {
+ if ( method == 'read' ) {
+ options = options || {};
+ options.context = this;
+ options.type = 'GET';
+ options.data = _.extend( options.data || {}, {
+ action: 'camptix-attendance',
+
+ camptix_action: 'sync-list',
+ camptix_paged: Math.floor( this.length / 50 ) + 1,
+ camptix_secret: _camptixAttendanceSecret
+ });
+
+ if ( this.query.search )
+ options.data.camptix_search = this.query.search;
+
+ if ( this.query.filters )
+ options.data.camptix_filters = this.query.filters;
+
+ return wp.ajax.send( options );
+ }
+ },
+
+ /**
+ * Returns true if this collection (potentially) has more items.
+ */
+ hasMore: function() {
+ return this._hasMore;
+ },
+
+ /**
+ * Get more items with this query.
+ */
+ more: function( options ) {
+ var that = this;
+
+ if ( ! this.hasMore() ) {
+ return $.Deferred().resolveWith( this ).promise();
+ }
+
+ if ( this._more && 'pending' === this._more.state() ) {
+ return this._more;
+ }
+
+ return this._more = this.fetch({ remove: false }).done( function( resp ) {
+ if ( _.isEmpty( resp ) || resp.length < 50 ) {
+ that._hasMore = false;
+ this.controller.trigger( 'more:toggle', this._hasMore );
+ }
+ });
+ }
+ });
+
+ /**
+ * Attendee View
+ *
+ * A view of a single attendee in a list.
+ */
+ camptix.views.AttendeeView = Backbone.View.extend({
+ tagName: 'li',
+ className: 'item',
+
+ template: wp.template( 'attendee' ),
+
+ events: {
+ 'fastClick': 'toggle'
+ },
+
+ initialize: function( options ) {
+ this.controller = options.controller;
+
+ this.listenTo( this.model, 'change', this.render );
+ this.listenTo( this.model, 'destroy', this.remove );
+ this.listenTo( this.model, 'camptix:sync:start', this.syncStart );
+ this.listenTo( this.model, 'camptix:sync:end', this.syncEnd );
+ },
+
+ /**
+ * Render the attendee list item.
+ */
+ render: function() {
+ this.$el.html( this.template( this.model.toJSON() ) );
+ return this;
+ },
+
+ /**
+ * Show a spinner.
+ */
+ syncStart: function() {
+ this.$el.addClass( 'camptix-loading' );
+ },
+
+ /**
+ * Hide the spinner.
+ */
+ syncEnd: function() {
+ this.$el.removeClass( 'camptix-loading' );
+ },
+
+ /**
+ * Open the Attendee Toggle modal.
+ */
+ toggle: function() {
+ // This touch was to stop a scroll.
+ if ( +new Date() - this.controller.lastScroll < 200 )
+ return;
+
+ var toggleView = new camptix.views.AttendeeToggleView({ model: this.model, controller: this.controller });
+ $(document.body).append( toggleView.render().el );
+ }
+ });
+
+ /**
+ * Attendee Toggle View
+ *
+ * The modal that pops up when an attendee is selected
+ * from the list. Here you can toggle their status.
+ */
+ camptix.views.AttendeeToggleView = Backbone.View.extend({
+ className: 'attendee-toggle-wrap',
+
+ template: wp.template( 'attendee-toggle' ),
+
+ events: {
+ 'fastClick .yes': 'yes',
+ 'fastClick .no': 'no',
+ 'fastClick .close': 'close'
+ },
+
+ initialize: function( options ) {
+ this.controller = options.controller;
+ this.$overlay = $('.overlay');
+ },
+
+ /**
+ * Render modal.
+ */
+ render: function() {
+ this.$el.html( this.template( this.model.toJSON() ) );
+ this.$overlay.show();
+ return this;
+ },
+
+ /**
+ * Set to attending.
+ */
+ yes: function() {
+ this.controller.trigger( 'flush' );
+ this.model.toggle( true );
+ return this.close();
+ },
+
+ /**
+ * Set to not attending.
+ */
+ no: function() {
+ this.controller.trigger( 'flush' );
+ this.model.toggle( false );
+ return this.close();
+ },
+
+ /**
+ * Close modal without changing any settings.
+ */
+ close: function() {
+ this.$overlay.hide();
+ this.remove();
+ return false;
+ }
+ });
+
+ /**
+ * Search View
+ *
+ * A search view invoked via Menu - Search.
+ */
+ camptix.views.AttendeeSearchView = Backbone.View.extend({
+ className: 'attendee-search-view',
+ template: wp.template( 'attendee-search' ),
+
+ events: {
+ 'input input': 'search',
+ 'keyup input': 'search',
+ 'change input': 'search',
+ 'search input': 'search',
+ 'fastClick .close': 'close'
+ },
+
+ initialize: function( options ) {
+ if ( options && options.controller ) {
+ this.controller = options.controller;
+ }
+
+ this.search = _.debounce( this.search, 500 );
+ },
+
+ /**
+ * Render Search view.
+ */
+ render: function() {
+ this.$el.html( this.template() );
+ return this;
+ },
+
+ /**
+ * Ask the controller to perform a new search.
+ */
+ search: function( event ) {
+ if ( event.keyCode == 13 ) {
+ this.$el.find( 'input' ).blur();
+ }
+
+ var keyword = event.target.value || '';
+ this.controller.trigger( 'search', keyword );
+ },
+
+ /**
+ * Close the view and reset search.
+ */
+ close: function() {
+ this.controller.trigger( 'search', '' );
+ this.remove();
+ }
+ });
+
+ /**
+ * Filter View
+ *
+ * Invoked via Menu - Filters.
+ */
+ camptix.views.AttendeeFilterView = Backbone.View.extend({
+ className: 'attendee-filter-view',
+ template: wp.template( 'attendee-filter' ),
+
+ events: {
+ 'fastClick .close': 'close',
+ 'fastClick .filter-attendance li': 'toggleAttendance',
+ 'fastClick .filter-tickets li': 'toggleTickets'
+ },
+
+ initialize: function( options ) {
+ this.controller = options.controller;
+ this.filterSettings = options.filterSettings || {};
+ },
+
+ /**
+ * Render the filters menu.
+ */
+ render: function() {
+ this.$el.html( this.template( this.filterSettings ) );
+ return this;
+ },
+
+ /**
+ * Close the filter screen.
+ */
+ close: function() {
+ this.remove();
+ },
+
+ /**
+ * Toggle items in the attendance status list.
+ */
+ toggleAttendance: function( event ) {
+ var selection = $( event.target ).data( 'attendance' );
+ this.filterSettings.attendance = selection;
+ this.render();
+
+ this.controller.trigger( 'filter', this.filterSettings );
+ },
+
+ /**
+ * Toggle items in the tickets list.
+ */
+ toggleTickets: function( event ) {
+ var ticket_id = $( event.target ).data( 'ticket-id' );
+
+ // Remove or append the ticket_id to the filter settings.
+ if ( _.contains( this.filterSettings.tickets, ticket_id ) ) {
+ this.filterSettings.tickets = _.without( this.filterSettings.tickets, ticket_id );
+ } else {
+ this.filterSettings.tickets.push( ticket_id );
+ }
+
+ this.render();
+ this.controller.trigger( 'filter', this.filterSettings );
+ },
+ });
+
+ /**
+ * Main Application View and controller.
+ */
+ camptix.views.Application = Backbone.View.extend({
+ template: wp.template( 'application' ),
+
+ /**
+ * Main Application events/controls.
+ */
+ events: {
+ 'fastClick .dashicons-menu': 'menu',
+ 'fastClick .submenu .search': 'searchView',
+ 'fastClick .submenu .refresh': 'refresh',
+ 'fastClick .submenu .filter': 'filterView'
+ },
+
+ /**
+ * Initialize the application.
+ */
+ initialize: function() {
+ this.cache = [];
+ this.query = {};
+ this.requests = [];
+ this.lastScroll = 0;
+
+ this.filterSettings = {
+ 'attendance': 'none',
+ 'tickets': _camptixAttendanceTickets,
+ 'search': ''
+ };
+
+ this.render();
+
+ this.$header = this.$el.find( 'header' );
+ this.$menu = this.$header.find( '.menu' );
+
+ this.scroll = _.chain( this.scroll ).bind( this ).value();
+ this.$list = this.$el.find( '.attendees-list' );
+ this.$list.on( 'scroll', this.scroll );
+ this.$loading = this.$list.find( '.loading' );
+
+ this.on( 'search', this.search, this );
+ this.on( 'flush', this.flush, this );
+ this.on( 'more:toggle', this.moreToggle, this );
+ this.on( 'filter', this.filter, this );
+
+ this.setupCollection();
+ },
+
+ /**
+ * Runs when hasMore is toggled in the current collection.
+ */
+ moreToggle: function( hasMore ) {
+ this.$loading.toggle( hasMore );
+ },
+
+ /**
+ * Setup a collection (or retrieve one from cache)
+ */
+ setupCollection: function( query ) {
+ var collection,
+ options = {};
+
+ // Dispose of the current collection and cache it for later use.
+ if ( 'undefined' != typeof this.collection ) {
+ this.collection.off( null, null, this );
+ this.cache.push( this.collection );
+ }
+
+ query = _.defaults( query || {}, {
+ search: '',
+ filters: _.clone( this.filterSettings )
+ });
+
+ options.query = query;
+ options.controller = this;
+
+ collection = _.find( this.cache, function( collection ) {
+ return _.isEqual( collection.query, options.query );
+ } );
+
+ if ( ! collection ) {
+ collection = new camptix.collections.AttendeesList( [], options );
+ }
+
+ this.query = query;
+ this.collection = collection;
+ this.collection.on( 'add', this.add, this );
+ this.collection.on( 'reset', this.reset, this );
+
+ // Clear the list before adding things back.
+ this.$list.find( 'li.item' ).remove();
+
+ if ( this.collection.length ) {
+ this.collection.trigger( 'reset' );
+ } else {
+ this.collection.more().done( this.scroll );
+ }
+
+ this.trigger( 'more:toggle', collection.hasMore() );
+ },
+
+ /**
+ * Scroll event handler.
+ */
+ scroll: function() {
+ var view = this,
+ el = this.$list[0];
+
+ this.lastScroll = +new Date();
+
+ if ( ! this.collection.hasMore() )
+ return;
+
+ if ( el.scrollHeight < el.scrollTop + ( el.clientHeight * 3 ) ) {
+ this.collection.more().done(function() {
+ view.scroll();
+ });
+ }
+ },
+
+ /**
+ * Render the application view.
+ */
+ render: function() {
+ this.$el.html( this.template() );
+ $(document.body).append( this.el );
+ return this;
+ },
+
+ /**
+ * Append a single AttendeeView item (from a model) to the list.
+ */
+ add: function( item ) {
+ var view = new camptix.views.AttendeeView({ model: item, controller: this });
+ this.$loading.before( view.render().el );
+ },
+
+ /**
+ * A collection is reset. Make sure everything is added back to the view.
+ */
+ reset: function() {
+ this.collection.each( this.add, this );
+ },
+
+ /**
+ * Toggle nav menu.
+ */
+ menu: function( event ) {
+ this.$menu.toggleClass( 'dropdown' );
+ },
+
+ /**
+ * Show the Search view.
+ */
+ searchView: function() {
+ this.$menu.removeClass( 'dropdown' );
+ this.searchView = new camptix.views.AttendeeSearchView({ controller: this });
+ this.$header.append( this.searchView.render().el );
+
+ this.searchView.$el.find('input').focus();
+ return false;
+ },
+
+ /**
+ * Show the Filter Settings view.
+ */
+ filterView: function() {
+ this.$menu.removeClass( 'dropdown' );
+ this.filterView = new camptix.views.AttendeeFilterView({ controller: this, filterSettings: this.filterSettings });
+ this.$el.append( this.filterView.render().el );
+ return false;
+ },
+
+ /**
+ * Remove everything from the list, flush all caches
+ * and setup a new collection with the current settings.
+ */
+ refresh: function() {
+ this.$menu.removeClass( 'dropdown' );
+ delete this.collection;
+ this.flush();
+ this.setupCollection();
+ return false;
+ },
+
+ /**
+ * Re-initialize a calloction with a search term.
+ */
+ search: function( keyword ) {
+ this.keyword = this.keyword || '';
+ if ( keyword == this.keyword )
+ return;
+
+ this.keyword = keyword;
+ this.setupCollection({ search: this.keyword });
+ },
+
+ /**
+ * Re-initialize a collection with (possibly) new filter settings.
+ */
+ filter: function( settings ) {
+ this.filterSettings = settings;
+ delete this.collection;
+ this.flush();
+ this.setupCollection();
+ },
+
+ /**
+ * Remove all queries from cache.
+ */
+ flush: function() {
+ this.cache = [];
+ }
+ });
+
+ // Initialize application.
+ camptix.app = new camptix.views.Application();
+});
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsassetsattendanceuiscss"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.scss</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.scss (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/attendance-ui.scss 2014-10-21 15:23:09 UTC (rev 919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,461 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,700);
+
+body {
+ background: #ccc;
+ font-family: 'Open Sans', Helvetica, sans-serif;
+}
+
+.overlay {
+ display: none;
+ position: absolute;
+ background: black;
+ opacity: 0.8;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+
+ z-index: 200;
+}
+
+header {
+ display: block;
+ position: absolute;
+ background: #333;
+ height: 50px;
+ top: 0;
+ left: 0;
+ right: 0;
+
+ color: white;
+ padding-left: 16px;
+
+ z-index: 100;
+}
+
+header h1,
+.attendee-filter-view h1 {
+ display: block;
+ clear: none;
+ line-height: 50px;
+ margin: 0;
+ font-size: 16px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-right: 100px;
+ color: white;
+}
+
+.menu {
+ float: right;
+ height: 50px;
+
+ .dashicons {
+ color: white;
+ float: left;
+ width: 50px;
+ height: 50px;
+ font-size: 28px;
+ line-height: 50px;
+ text-align: center;
+ }
+}
+
+.loading {
+ line-height: 50px;
+ color: #999;
+
+ .spinner-container {
+ width: 50px;
+ height: 50px;
+ float: left;
+ overflow: hidden;
+ text-align: center;
+ }
+
+ .spinner {
+ display: inline-block;
+ margin-top: 14px;
+
+ border-top: 5px solid rgba(200, 200, 200, 0.3);
+ border-right: 5px solid rgba(200, 200, 200, 0.3);
+ border-bottom: 5px solid rgba(200, 200, 200, 0.3);
+ border-left: 5px solid #ccc;
+
+ -webkit-animation: load8 1.1s infinite linear;
+ animation: load8 1.1s infinite linear;
+ }
+
+ .spinner,
+ .spinner:after {
+ border-radius: 50%;
+ width: 12px;
+ height: 12px;
+ }
+}
+
+li.item {
+ .spinner-container {
+ display: none;
+ width: 50px;
+ height: 50px;
+ background: white;
+ overflow: hidden;
+ position: absolute;
+ left: 0;
+ }
+
+ &.camptix-loading .spinner-container {
+ display: block;
+ }
+
+ .spinner,
+ .spinner:before,
+ .spinner:after {
+ display: block;
+ background: #ccc;
+ -webkit-animation: load1 1s infinite ease-in-out;
+ animation: load1 1s infinite ease-in-out;
+ width: 5px;
+ height: 10px;
+ }
+
+ .spinner:before,
+ .spinner:after {
+ position: absolute;
+ top: 0;
+ content: '';
+ }
+
+ .spinner:before {
+ left: 7px;
+ }
+
+ .spinner {
+ margin-left: 22px;
+ margin-top: 20px;
+
+ position: relative;
+ font-size: 11px;
+ -webkit-animation-delay: -0.16s;
+ animation-delay: -0.16s;
+ }
+
+ .spinner:after {
+ left: -7px;
+ -webkit-animation-delay: -0.32s;
+ animation-delay: -0.32s;
+ }
+}
+
+.menu .submenu {
+ display: none;
+ position: absolute;
+ top: 50px;
+ right: 0;
+ background: #444;
+ min-height: 50px;
+
+ a {
+ height: 51px;
+ line-height: 50px;
+ text-decoration: none;
+ color: white;
+ padding: 0 20px;
+ display: block;
+
+ min-width: 150px;
+ max-width: 200px;
+
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+}
+
+.menu.dropdown .dashicons-menu {
+ background: #444;
+}
+
+.menu.dropdown .submenu {
+ display: block;
+}
+
+.attendee-search-view,
+.attendee-filter-view {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ height: 50px;
+ background: #444;
+ color: white;
+
+ .wrapper:before {
+ content: "\f179";
+ font-family: 'dashicons';
+ -webkit-font-smoothing: antialiased;
+ font-size: 28px;
+ height: 50px;
+ width: 50px;
+ left: 0;
+ position: absolute;
+ line-height: 50px;
+ text-align: center;
+ }
+
+ .close {
+ float: right;
+ color: white;
+ width: 50px;
+ height: 50px;
+ font-size: 28px;
+ line-height: 50px;
+ text-align: center;
+ }
+}
+
+.attendee-search-view .wrapper {
+ height: 50px;
+ display: block;
+ margin-right: 50px;
+ margin-left: 50px;
+}
+
+.attendee-search-view input {
+ border: 0;
+ font-size: 16px;
+ font-family: 'Open Sans', Helvetica, sans-serif;
+ height: 35px;
+ margin-top: 7px;
+ padding: 0 8px;
+ width: 100%;
+
+ margin-left: -4px;
+}
+
+.attendee-filter-view {
+ z-index: 500;
+ height: auto;
+ bottom: 0;
+ overflow: scroll;
+
+ .wrapper:before {
+ content: "\f180";
+ }
+
+ h1 {
+ padding-left: 50px;
+ }
+
+ h1.section-title {
+ padding-left: 18px;
+ border-bottom: solid 1px #555;
+ margin: 0;
+ }
+
+ ul.section-controls {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ li {
+ height: 50px;
+ line-height: 50px;
+ margin: 0;
+ border-bottom: solid 1px #555;
+ padding-left: 50px;
+ padding-right: 50px;
+ cursor: pointer;
+
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ li.selected:before {
+ content: "\f147";
+ font-family: 'dashicons';
+ -webkit-font-smoothing: antialiased;
+ font-size: 28px;
+ height: 50px;
+ width: 50px;
+ left: 0;
+ position: absolute;
+ line-height: 50px;
+ text-align: center;
+ }
+ }
+}
+
+.attendees-list {
+ margin: 0;
+ padding: 0;
+ background: white;
+ position: absolute;
+ top: 50px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+
+ li {
+ display: block;
+ float: left;
+ width: 100%;
+ height: 50px;
+ border-bottom: solid 1px #ddd;
+
+ -webkit-tap-highlight-color: rgba( 0,0,0,0 );
+
+ &.item {
+ cursor: pointer;
+ }
+ }
+
+ .toggle {
+ display: block;
+ float: left;
+ width: 50px;
+ height: 50px;
+
+ -webkit-tap-highlight-color: rgba( 0,0,0,0 );
+
+ .dashicons {
+ font-size: 24px;
+ line-height: 50px;
+ height: 50px;
+ width: 50px;
+ text-align: center;
+ text-decoration: none;
+ color: #ddd;
+ }
+
+ &.yes .dashicons {
+ color: #0acc00;
+ }
+ }
+
+ .name {
+ display: block;
+ margin-left: 50px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ height: 50px;
+ line-height: 50px;
+ font-size: 16px;
+ padding-left: 2px;
+ padding-right: 16px;
+ }
+}
+
+.attendee-toggle-wrap {
+ margin: 0;
+ padding: 0;
+ background: white;
+ position: absolute;
+ top: 20px;
+ bottom: 20px;
+ left: 20px;
+ right: 20px;
+ overflow: scroll;
+ padding: 50px 40px;
+ text-align: center;
+
+ z-index: 300;
+
+ .yes-no-container {
+ height: 50px;
+ display: block;
+ width: 200px;
+ margin: 40px auto 0 auto;
+ }
+
+ a.yes,
+ a.no {
+ height: 50px;
+ display: block;
+ float: left;
+ width: 50%;
+ line-height: 50px;
+ font-size: 16px;
+ background: #333;
+ color: white;
+ text-decoration: none;
+ }
+
+ a.yes {
+ background: #0acc00;
+ }
+
+ .close {
+ font-size: 24px;
+ line-height: 50px;
+ height: 50px;
+ width: 50px;
+ text-align: center;
+ text-decoration: none;
+ color: #444;
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+
+ img {
+ background: #ccc;
+ width: 150px;
+ height: 150px;
+ }
+}
+
+/** Animations, courtesy of https://github.com/lukehaas/css-loaders **/
+
+@-webkit-keyframes load1 {
+ 0%,
+ 80%,
+ 100% {
+ box-shadow: 0 0 #ccc;
+ height: 10px;
+ }
+ 40% {
+ box-shadow: 0 -5px #ccc;
+ height: 18px;
+ }
+}
+@keyframes load1 {
+ 0%,
+ 80%,
+ 100% {
+ box-shadow: 0 0 #ccc;
+ height: 10px;
+ }
+ 40% {
+ box-shadow: 0 -5px #ccc;
+ height: 18px;
+ }
+}
+
+@-webkit-keyframes load8 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@keyframes load8 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsassetsjqueryfastbuttonjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/jquery.fastbutton.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/jquery.fastbutton.js (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/assets/jquery.fastbutton.js 2014-10-21 15:23:09 UTC (rev 919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,154 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+(function() {
+ /**
+ * From: http://code.this.com/mobile/articles/fast_buttons.html
+ * Also see: http://stackoverflow.com/questions/6300136/trying-to-implement-googles-fast-button
+ */
+
+ /** For IE8 and earlier compatibility: https://developer.mozilla.org/en/DOM/element.addEventListener */
+ function addListener(el, type, listener, useCapture) {
+ if (el.addEventListener) {
+ el.addEventListener(type, listener, useCapture);
+ return {
+ destroy: function() { el.removeEventListener(type, listener, useCapture); }
+ };
+ } else {
+ // see: http://stackoverflow.com/questions/5198845/javascript-this-losing-context-in-ie
+ var handler = function(e) { listener.handleEvent(window.event, listener); }
+ el.attachEvent('on' + type, handler);
+
+ return {
+ destroy: function() { el.detachEvent('on' + type, handler); }
+ };
+ }
+ }
+
+ var isTouch = "ontouchstart" in window;
+
+ /* Construct the FastButton with a reference to the element and click handler. */
+ this.FastButton = function(element, handler, useCapture) {
+ // collect functions to call to cleanup events
+ this.events = [];
+ this.touchEvents = [];
+ this.element = element;
+ this.handler = handler;
+ this.useCapture = useCapture;
+ if (isTouch)
+ this.events.push(addListener(element, 'touchstart', this, this.useCapture));
+ this.events.push(addListener(element, 'click', this, this.useCapture));
+ };
+
+ /* Remove event handling when no longer needed for this button */
+ this.FastButton.prototype.destroy = function() {
+ for (i = this.events.length - 1; i >= 0; i -= 1)
+ this.events[i].destroy();
+ this.events = this.touchEvents = this.element = this.handler = this.fastButton = null;
+ };
+
+ /* acts as an event dispatcher */
+ this.FastButton.prototype.handleEvent = function(event) {
+ switch (event.type) {
+ case 'touchstart': this.onTouchStart(event); break;
+ case 'touchmove': this.onTouchMove(event); break;
+ case 'touchend': this.onClick(event); break;
+ case 'click': this.onClick(event); break;
+ }
+ };
+
+ /* Save a reference to the touchstart coordinate and start listening to touchmove and
+ touchend events. Calling stopPropagation guarantees that other behaviors don’t get a
+ chance to handle the same click event. This is executed at the beginning of touch. */
+ this.FastButton.prototype.onTouchStart = function(event) {
+ event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true);
+ this.touchEvents.push(addListener(this.element, 'touchend', this, this.useCapture));
+ this.touchEvents.push(addListener(document.body, 'touchmove', this, this.useCapture));
+ this.startX = event.touches[0].clientX;
+ this.startY = event.touches[0].clientY;
+ };
+
+ /* When /if touchmove event is invoked, check if the user has dragged past the threshold of 10px. */
+ this.FastButton.prototype.onTouchMove = function(event) {
+ if (Math.abs(event.touches[0].clientX - this.startX) > 10 || Math.abs(event.touches[0].clientY - this.startY) > 10) {
+ this.reset(); //if he did, then cancel the touch event
+ }
+ };
+
+ /* Invoke the actual click handler and prevent ghost clicks if this was a touchend event. */
+ this.FastButton.prototype.onClick = function(event) {
+ event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true);
+ this.reset();
+ // Use .call to call the method so that we have the correct "this": https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call
+ var result = this.handler.call(this.element, event);
+ if (event.type == 'touchend')
+ clickbuster.preventGhostClick(this.startX, this.startY);
+ return result;
+ };
+
+ this.FastButton.prototype.reset = function() {
+ for (i = this.touchEvents.length - 1; i >= 0; i -= 1)
+ this.touchEvents[i].destroy();
+ this.touchEvents = [];
+ };
+
+ this.clickbuster = function() {}
+
+ /* Call preventGhostClick to bust all click events that happen within 25px of
+ the provided x, y coordinates in the next 2.5s. */
+ this.clickbuster.preventGhostClick = function(x, y) {
+ clickbuster.coordinates.push(x, y);
+ window.setTimeout(clickbuster.pop, 2500);
+ };
+
+ this.clickbuster.pop = function() {
+ clickbuster.coordinates.splice(0, 2);
+ };
+
+ /* If we catch a click event inside the given radius and time threshold then we call
+ stopPropagation and preventDefault. Calling preventDefault will stop links
+ from being activated. */
+ this.clickbuster.onClick = function(event) {
+ for (var i = 0; i < clickbuster.coordinates.length; i += 2) {
+ var x = clickbuster.coordinates[i];
+ var y = clickbuster.coordinates[i + 1];
+ if (Math.abs(event.clientX - x) < 25 && Math.abs(event.clientY - y) < 25) {
+ event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true);
+ event.preventDefault ? event.preventDefault() : (event.returnValue=false);
+ }
+ }
+ };
+
+ if (isTouch) {
+ // Don't need to use our custom addListener function since we only bust clicks on touch devices
+ document.addEventListener('click', clickbuster.onClick, true);
+ clickbuster.coordinates = [];
+ }
+})(this);
+
+(function($) {
+ $.event.special.fastClick = {
+ setup: function () {
+ $(this).data('fastClick', new FastButton(this, $.event.special.fastClick.handler));
+ },
+ teardown: function () {
+ $(this).data('fastClick').destroy();
+ $(this).removeData('fastClick');
+ },
+ handler: function (e) {
+ // convert native event to jquery event
+ e = $.event.fix(e);
+ e.type = 'fastClick';
+
+ /*
+ event.handle is deprecated and removed as of version 1.9
+ use event.dispatch instead,
+ $.event.handle.apply(this, arguments);
+ */
+ $.event.dispatch.apply(this, arguments);
+ }
+ };
+
+ $.fn.fastClick = function(fn) {
+ return $(this).each(function() {
+ return fn ? $(this).bind("fastClick", fn) : $(this).trigger("fastClick");
+ });
+ };
+}(jQuery));
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsattendanceuiphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/attendance-ui.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/attendance-ui.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/attendance-ui.php 2014-10-21 15:23:09 UTC (rev 919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,98 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Something here
+ */
+
+global $camptix, $wp_scripts, $wp_styles;
+
+$camptix_tickets = $camptix->tmp( 'attendance_tickets' );
+$camptix_options = $camptix->get_options();
+?>
+<html>
+<head>
+ <title><?php printf( __( '%s Attendance', 'camptix' ), esc_html( $camptix_options['event_name'] ) ); ?></title>
+
+ <?php $wp_scripts->do_items( array( 'camptix-attendance-ui' ) ); ?>
+ <?php $wp_styles->do_items( array( 'camptix-attendance-ui' ) ); ?>
+ <script>
+ _camptixAttendanceSecret = '<?php echo esc_js( $_GET['camptix-attendance'] ); ?>';
+ _camptixAttendanceTickets = [ <?php echo esc_js( implode( ', ', array_map( 'absint', wp_list_pluck( $camptix_tickets, 'ID' ) ) ) ); ?> ];
+ </script>
+
+ <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
+ <meta name="referrer" content="never" />
+</head>
+<body>
+ <script id="tmpl-attendee" type="text/template">
+ <div class="spinner-container"><span class="spinner"></span></div>
+ <a href="#" class="status toggle <# if ( data.status ) { #> yes <# } #>"><div class="dashicons dashicons-admin-users"></div></a>
+ <span class="name">
+ {{ data.name }}
+ </span>
+ </script>
+
+ <script id="tmpl-attendee-toggle" type="text/template">
+ <img src="{{ data.avatar }}" />
+ <p>Did <strong>{{ data.name }}</strong> attend <?php echo esc_html( $camptix_options['event_name'] ); ?>?</p>
+
+ <div class="yes-no-container">
+ <a href="#" class="yes">Yes</a>
+ <a href="#" class="no">No</a>
+ </div>
+
+ <a href="#" class="close dashicons dashicons-no"></a>
+ </script>
+
+ <script id="tmpl-application" type="text/template">
+ <div class="overlay"></div>
+
+ <header>
+ <div class="menu">
+ <a href="#" class="dashicons dashicons-menu"></a>
+ <div class="submenu">
+ <a href="#" class="search">Search</a>
+ <a href="#" class="filter">Filter</a>
+ <a href="#" class="refresh">Refresh</a>
+ </div>
+ </div>
+ <h1><?php echo esc_html( $camptix_options['event_name'] ); ?></h1>
+ </header>
+
+ <div id="attendees-list-wrapper">
+ <ul class="attendees-list">
+ <li class="loading">
+ <div class="spinner-container"><span class="spinner"></span></div>
+ <span>Loading...</span>
+ </li>
+ </ul>
+ </div>
+ </script>
+
+ <script id="tmpl-attendee-search" type="text/template">
+ <a href="#" class="close dashicons dashicons-no"></a>
+ <div class="wrapper">
+ <input type="text" autocomplete="off" placeholder="Search" />
+ </div>
+ </script>
+
+ <script id="tmpl-attendee-filter" type="text/template">
+ <a href="#" class="close dashicons dashicons-no"></a>
+ <div class="wrapper">
+ <h1>Filters</h1>
+
+ <h1 class="section-title">Attendance</h1>
+ <ul class="filter-attendance section-controls">
+ <li data-attendance="none" <# if ( data.attendance == 'none' ) { #> class="selected" <# } #> >All</li>
+ <li data-attendance="attending" <# if ( data.attendance == 'attending' ) { #> class="selected" <# } #> >Attending</li>
+ <li data-attendance="not-attending" <# if ( data.attendance == 'not-attending' ) { #> class="selected" <# } #> >Not Attending</li>
+ </ul>
+
+ <h1 class="section-title">Tickets</h1>
+ <ul class="filter-tickets section-controls">
+ <?php foreach ( $camptix_tickets as $ticket ) : ?>
+ <li data-ticket-id="<?php echo absint( $ticket->ID ); ?>" <# if ( _.contains( data.tickets, <?php echo absint( $ticket->ID ); ?> ) ) { #> class="selected" <# } #> ><?php echo esc_html( $ticket->post_title ); ?></li>
+ <?php endforeach; ?>
+ </ul>
+ </div>
+ </script>
+</body>
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendanceaddonsattendancephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/attendance.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/attendance.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/addons/attendance.php 2014-10-21 15:23:09 UTC (rev 919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,349 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Allows event organizers to track which attendees showed up to the event.
+ */
+class CampTix_Attendance extends CampTix_Addon {
+ /**
+ * Runs during CampTix init.
+ */
+ public function camptix_init() {
+ global $camptix;
+
+ // Admin Settings UI.
+ if ( current_user_can( $camptix->caps['manage_options'] ) ) {
+ add_filter( 'camptix_setup_sections', array( $this, 'setup_sections' ) );
+ add_action( 'camptix_menu_setup_controls', array( $this, 'setup_controls' ), 10, 1 );
+ add_filter( 'camptix_validate_options', array( $this, 'validate_options' ), 10, 2 );
+ }
+
+ $camptix_options = $camptix->get_options();
+ if ( empty( $camptix_options['attendance-secret'] ) )
+ return;
+
+ $this->secret = $camptix_options['attendance-secret'];
+
+ if ( empty( $camptix_options['attendance-enabled'] ) )
+ return;
+
+ add_filter( 'wp_ajax_camptix-attendance', array( $this, 'ajax_callback' ) );
+ add_filter( 'wp_ajax_nopriv_camptix-attendance', array( $this, 'ajax_callback' ) );
+
+ if ( ! empty( $_GET['camptix-attendance'] ) && $_GET['camptix-attendance'] == $this->secret ) {
+ add_filter( 'template_include', array( $this, 'setup_attendance_ui' ) );
+ }
+ }
+
+ /**
+ * Initialize the Attendance UI.
+ *
+ * Enqueue all necessary scripts and styles, pass any needed data
+ * via $camptix->tmp(). Note that previously enqueued scripts and
+ * styles will not be loaded.
+ */
+ public function setup_attendance_ui( $template ) {
+ global $camptix;
+
+ wp_enqueue_script( 'jquery-fastbutton', plugins_url( '/assets/jquery.fastbutton.js', __FILE__ ), array( 'jquery' ) );
+ wp_enqueue_script( 'camptix-attendance-ui', plugins_url( '/assets/attendance-ui.js' , __FILE__ ), array( 'backbone', 'jquery', 'wp-util', 'jquery-fastbutton' ) );
+ wp_enqueue_style( 'camptix-attendance-ui', plugins_url( '/assets/attendance-ui.css', __FILE__ ), array( 'dashicons' ) );
+
+ $camptix->tmp( 'attendance_tickets', $this->get_tickets() );
+ return dirname( __FILE__ ) . '/attendance-ui.php';
+ }
+
+ /**
+ * Callback/router for an AJAX Request.
+ *
+ * Routes to the appropriate callback method depending
+ * on the requested CampTix action. Also validates keys.
+ */
+ public function ajax_callback() {
+ if ( empty( $_REQUEST['camptix_secret'] ) || $_REQUEST['camptix_secret'] != $this->secret )
+ return;
+
+ $action = $_REQUEST['camptix_action'];
+ if ( 'sync-model' == $action ) {
+ return $this->_ajax_sync_model();
+ } elseif ( 'sync-list' == $action ) {
+ return $this->_ajax_sync_list();
+ }
+ }
+
+ /**
+ * Synchronize an attendee model.
+ *
+ * Sets are removes the attended flag for a given camptix_id.
+ */
+ public function _ajax_sync_model() {
+ if ( empty( $_REQUEST['camptix_id'] ) )
+ return;
+
+ $attendee_id = absint( $_REQUEST['camptix_id'] );
+ $attendee = get_post( $attendee_id );
+
+ if ( ! $attendee || 'tix_attendee' != $attendee->post_type || 'publish' != $attendee->post_status )
+ return;
+
+ if ( isset( $_REQUEST['camptix_set_attendance'] ) ) {
+ if ( 'true' == $_REQUEST['camptix_set_attendance'] ) {
+ $this->log( 'Marked attendee as attended.', $attendee->ID );
+ update_post_meta( $attendee->ID, 'tix_attended', true );
+ } else {
+ $this->log( 'Marked attendee as did not attended.', $attendee->ID );
+ delete_post_meta( $attendee->ID, 'tix_attended' );
+ }
+ }
+
+ return wp_send_json_success( array( $this->_make_object( $attendee ) ) );
+ }
+
+ /**
+ * Synchronize an attendee list.
+ *
+ * Queries the database for attendees given a query and
+ * returns a batch back to Backbone.sync.
+ */
+ public function _ajax_sync_list() {
+ global $wpdb;
+
+ $paged = 1;
+ if ( ! empty( $_REQUEST['camptix_paged'] ) )
+ $paged = absint( $_REQUEST['camptix_paged'] );
+
+ $ticket_ids = wp_list_pluck( $this->get_tickets(), 'ID' );
+
+ $query_args = array(
+ 'post_type' => 'tix_attendee',
+ 'post_status' => 'publish',
+ 'orderby' => 'title',
+ 'order' => 'ASC',
+ 'paged' => $paged,
+ 'posts_per_page' => 50,
+ 'meta_query' => '',
+ );
+
+ $filters = array();
+ if ( ! empty( $_REQUEST['camptix_filters'] ) )
+ $filters = (array) $_REQUEST['camptix_filters'];
+
+ $filters = wp_parse_args( (array) $_REQUEST['camptix_filters'], array(
+ 'attendance' => 'none',
+ 'tickets' => array(),
+ ) );
+
+ $filters['search'] = ! empty( $_REQUEST['camptix_search'] ) ? $_REQUEST['camptix_search'] : '';
+
+ // Filter by attendance.
+ if ( in_array( $filters['attendance'], array( 'attending', 'not-attending' ) ) )
+ $this->_filter_query_attendance( $filters['attendance'] );
+
+ // Filter by ticket type.
+ $filters['tickets'] = array_intersect( $filters['tickets'], $ticket_ids );
+ if ( count( array_diff( $ticket_ids, $filters['tickets'] ) ) > 0 ) {
+
+ // No tickets selected.
+ if ( empty( $filters['tickets'] ) )
+ return wp_send_json_success( array() );
+
+ $this->_filter_query_tickets( $filters['tickets'] );
+ }
+
+ // Filter by search query.
+ if ( ! empty( $filters['search'] ) )
+ $this->_filter_query_search( $filters['search'] );
+
+ $query_args['suppress_filters'] = false;
+ $attendees = get_posts( $query_args );
+
+ $output = array();
+ foreach ( $attendees as $attendee ) {
+ $output[] = $this->_make_object( $attendee );
+ }
+
+ return wp_send_json_success( $output );
+ }
+
+ /**
+ * Helper method to make an Attendee object.
+ *
+ * Use this helper to return only the necessary data back
+ * with an AJAX method.
+ */
+ public function _make_object( $attendee ) {
+ $attendee = get_post( $attendee );
+
+ $first_name = get_post_meta( $attendee->ID, 'tix_first_name', true );
+ $last_name = get_post_meta( $attendee->ID, 'tix_last_name', true );
+ $avatar_url = sprintf( 'https://secure.gravatar.com/avatar/%s?s=160', md5( get_post_meta( $attendee->ID, 'tix_email', true ) ) );
+ $avatar_url = add_query_arg( 'd', 'https://secure.gravatar.com/avatar/ad516503a11cd5ca435acc9bb6523536?s=160', $avatar_url );
+
+ $status = (bool) get_post_meta( $attendee->ID, 'tix_attended', true );
+
+ return array(
+ 'id' => $attendee->ID,
+ 'name' => sprintf( '%s %s', $first_name, $last_name ),
+ 'avatar' => esc_url_raw( $avatar_url ),
+ 'status' => $status,
+ );
+ }
+
+ /**
+ * Filter the SQL in WP_Query for Search.
+ *
+ * Prior to 4.1 WordPress didn't have nested meta queries, so
+ * we're left with our own JOINs and WHEREs to look for a search
+ * query under various meta keys.
+ */
+ public function _filter_query_search( $search ) {
+ add_filter( 'posts_clauses', function( $clauses ) use ( $search ) {
+ global $wpdb;
+
+ $search = $wpdb->esc_like( $search );
+
+ $clauses['join'] .= "
+ INNER JOIN $wpdb->postmeta tix_first_name ON ( ID = tix_first_name.post_id AND tix_first_name.meta_key = 'tix_first_name' )
+ INNER JOIN $wpdb->postmeta tix_last_name ON ( ID = tix_last_name.post_id AND tix_last_name.meta_key = 'tix_last_name' )
+ ";
+
+ $clauses['where'] .= $wpdb->prepare( "
+ AND ( tix_first_name.meta_value LIKE '%%%s%%' OR tix_last_name.meta_value LIKE '%%%s%%' )
+ ", $search, $search );
+
+ return $clauses;
+ } );
+ }
+
+ /**
+ * Filter WP_Query to include only specific tickets.
+ */
+ public function _filter_query_tickets( $ticket_ids ) {
+ add_filter( 'posts_clauses', function( $clauses ) use ( $ticket_ids ) {
+ global $wpdb;
+
+ $clauses['join'] .= " INNER JOIN $wpdb->postmeta tix_ticket_id ON ( ID = tix_ticket_id.post_id AND tix_ticket_id.meta_key = 'tix_ticket_id' ) ";
+ $clauses['where'] .= sprintf( " AND ( tix_ticket_id.meta_value IN ( %s ) ) ", implode( ', ', array_map( 'absint', $ticket_ids ) ) );
+ return $clauses;
+ } );
+ }
+
+ /**
+ * Filter WP_Query to include only attending or non-attending attendees.
+ */
+ public function _filter_query_attendance( $attendance ) {
+ add_filter( 'posts_clauses', function( $clauses ) use ( $attendance ) {
+ global $wpdb;
+
+ $clauses['join'] .= " LEFT JOIN $wpdb->postmeta tix_attended ON ( ID = tix_attended.post_id AND tix_attended.meta_key = 'tix_attended' ) ";
+
+ if ( 'attending' == $attendance )
+ $clauses['where'] .= " AND ( tix_attended.meta_value = 1 ) ";
+ else
+ $clauses['where'] .= " AND ( tix_attended.meta_value IS NULL ) ";
+
+ return $clauses;
+ } );
+ }
+
+ /**
+ * Add a new section to the Setup screen.
+ */
+ public function setup_sections( $sections ) {
+ $sections['attendance-ui'] = __( 'Attendance UI', 'camptix' );
+ return $sections;
+ }
+
+ /**
+ * Add some controls to our Setup section.
+ */
+ public function setup_controls( $section ) {
+ global $camptix;
+
+ if ( 'attendance-ui' != $section )
+ return;
+
+ add_settings_section( 'general', __( 'Attendance UI', 'camptix' ), array( $this, 'setup_controls_section' ), 'camptix_options' );
+
+ // Fields
+ $camptix->add_settings_field_helper( 'attendance-enabled', __( 'Enabled', 'camptix' ), 'field_yesno', 'general',
+ __( "Don't forget to disable the UI after the event is over.", 'camptix' )
+ );
+
+ add_settings_field( 'attendance-secret', __( 'Secret Link' ), array( $this, 'field_secret' ), 'camptix_options', 'general' );
+ }
+
+ /**
+ * Secret Link Field
+ *
+ * This is a field that only shows the secret URL, and also has
+ * a "generate" checkbox that allows users to generate a new secret.
+ */
+ public function field_secret() {
+ $secret_url = ! empty( $this->secret ) ? add_query_arg( 'camptix-attendance', $this->secret, home_url() ) : '';
+ ?>
+ <input type="hidden" name="camptix_options[attendance-secret]" value="1" />
+ <textarea class="large-text" rows="4" disabled="disabled"><?php echo esc_textarea( $secret_url ); ?></textarea>
+
+ <input id="camptix-attendance-generate" type="checkbox" name="camptix_options[attendance-generate]" value="1" />
+ <label for="camptix-attendance-generate"><?php _e( 'Generate a new secret link (old links will expire)', 'camptix' ); ?></label>
+ <?php
+ }
+
+ /**
+ * Setup section description.
+ */
+ public function setup_controls_section() {
+ ?>
+ <p>The Attendance UI addon is useful for tracking attendance at the event. It allows registration volunteers to access a mobile-friendly UI during the event, and mark attendees as "attended" or "did not attend" as they register. The UI also offers live search and filters for your convenience.</p>
+
+ <p><strong>Note</strong>: Anyone with the secret link can access the attendance UI and change attendance data. Please keep this URL secret and change it if necessary.</p>
+ <?php
+ }
+
+ /**
+ * Runs whenever the CampTix option is updated.
+ */
+ public function validate_options( $output, $input ) {
+ if ( isset( $input['attendance-enabled'] ) )
+ $output['attendance-enabled'] = (bool) $input['attendance-enabled'];
+
+ if ( ! empty( $input['attendance-generate'] ) )
+ $output['attendance-secret'] = wp_generate_password( 32, false, false );
+
+ return $output;
+ }
+
+ /**
+ * Get CampTix Tickets
+ *
+ * Returns an array of published tickets registered with CampTix.
+ */
+ public function get_tickets() {
+ if ( isset( $this->tickets ) )
+ return $this->tickets;
+
+ $this->tickets = get_posts( array(
+ 'post_type' => 'tix_ticket',
+ 'post_status' => 'publish',
+ 'posts_per_page' => -1,
+ ) );
+
+ return $this->tickets;
+ }
+
+ /**
+ * Write a log entry to CampTix.
+ */
+ public function log( $message, $post_id = 0, $data = null ) {
+ global $camptix;
+ $camptix->log( $message, $post_id, $data, 'attendance' );
+ }
+
+ /**
+ * Register self as a CampTix addon.
+ */
+ public static function register_addon() {
+ camptix_register_addon( __CLASS__ );
+ }
+}
+
+CampTix_Attendance::register_addon();
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixattendancecamptixattendancephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/camptix-attendance.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/camptix-attendance.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-attendance/camptix-attendance.php 2014-10-21 15:23:09 UTC (rev 919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,13 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Plugin Name: CampTix - Attendance UI
+ * Description: An addon for CampTix that allows admins and volunteers to track whether a ticket holder attended the event via a mobile UI.
+ * Version: 0.1
+ * Author: Konstantin Kovshenin
+ * Author URI: http://kovshenin.com
+ */
+
+add_action( 'camptix_load_addons', 'camptix_attendance_register' );
+function camptix_attendance_register() {
+ require_once( plugin_dir_path( __FILE__ ) . 'addons/attendance.php' );
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre>
</div>
</div>
</body>
</html>