├── _config.yml ├── .gitignore ├── README.md ├── images └── office.svg ├── index.html └── javascripts └── app.js /_config.yml: -------------------------------------------------------------------------------- 1 | baseurl: "/abq-seatmap" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | .DS_Store 3 | Gemfile 4 | Gemfile.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # abq-seatmap 2 | Prototype: which seats are available in a building 3 | -------------------------------------------------------------------------------- /images/office.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ABQ SeatMap 11 | 12 | 33 | 34 | 35 | 36 | 37 |
38 |
39 | 40 | 46 | 47 | 61 | 62 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /javascripts/app.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | /* 4 | TODO: 5 | - better way to select/multiselect a model in a collection 6 | https://github.com/derickbailey/backbone.picky 7 | https://github.com/hashchange/backbone.select 8 | */ 9 | 10 | var Seat = Backbone.Model.extend({ 11 | 12 | }); 13 | 14 | var SeatCollection = Backbone.Collection.extend({ 15 | model: Seat, 16 | selected: null 17 | }); 18 | 19 | var SeatMap = Backbone.View.extend({ 20 | events: { 21 | 'click .desk': 'onClick' 22 | }, 23 | 24 | initialize: function(options) { 25 | this.seats = options.seats; 26 | 27 | // hack!! (so that we can apply CSS styles as normal) 28 | // get the raw SVG from the embedded image, 29 | // move it inline in the HTML document 30 | var doc = this.el.contentDocument; 31 | var $svg = $(doc).children('svg').eq(0); 32 | $svg.insertAfter(this.$el); 33 | 34 | // temporarily keep a reference to the current DOM element 35 | var $el = this.$el; 36 | 37 | // change this View to the new DOM element 38 | this.setElement($svg[0]); 39 | 40 | // remove the old DOM element 41 | $el.remove(); 42 | 43 | // render 44 | this.render(); 45 | 46 | this.listenTo(this.seats, 'change', this.render); 47 | this.listenTo(this.seats, 'hilite', this.hiliteSeats); 48 | }, 49 | 50 | hiliteSeats: function(ids) { 51 | var seats = this.seats; 52 | var $el = this.$el; 53 | 54 | if (ids && ids.length) { 55 | _.each(ids, function(id) { 56 | var $seat = $el.find('#'+id); 57 | $seat[0].classList.add('hilite'); 58 | }); 59 | } 60 | else { 61 | this.seats.each(function(seat) { 62 | var $seat = $el.find('#'+seat.id); 63 | $seat[0].classList.remove('hilite'); 64 | }); 65 | } 66 | }, 67 | 68 | onClick: function(e) { 69 | var id = e.target.id; 70 | this.seats.selected = id; 71 | this.seats.trigger('selected', id); 72 | }, 73 | 74 | render: function() { 75 | // TODO: find a more compatible way to addClass to svg 76 | // http://keith-wood.name/svg.html#dom 77 | // https://github.com/toddmotto/lunar 78 | 79 | var classNames = { 80 | "available": "available", 81 | "occupied": "occupied" 82 | }; 83 | 84 | var $el = this.$el; 85 | 86 | this.seats.each(function(seat) { 87 | var $seat = $el.find('#'+seat.id); 88 | if (!$seat[0]) { 89 | // no DOM element 90 | return; 91 | } 92 | var status = seat.get('status'); 93 | var className = classNames[status]; 94 | if (className) { 95 | $seat[0].classList.add(className); 96 | } 97 | }); 98 | 99 | return this; 100 | } 101 | }); 102 | 103 | var SeatDashboard = Backbone.View.extend({ 104 | events: { 105 | 'mouseenter .seat-legend.available': 'hiliteAvailableSeats', 106 | 'mouseenter .seat-legend.occupied': 'hiliteOccupiedSeats', 107 | 'mouseleave .seat-legend': 'hiliteNone' 108 | }, 109 | 110 | initialize: function(options) { 111 | this.seats = options.seats; 112 | 113 | this.listenTo(this.seats, 'change', this.render); 114 | 115 | this.render(); 116 | }, 117 | 118 | hiliteAvailableSeats: function() { 119 | this.hiliteSeatsWhere({status: 'available'}); 120 | }, 121 | 122 | hiliteOccupiedSeats: function() { 123 | this.hiliteSeatsWhere({status: 'occupied'}); 124 | }, 125 | 126 | hiliteSeatsWhere: function(attrs) { 127 | var seats = this.seats.where(attrs); 128 | var ids = _.pluck(seats, 'id'); 129 | this.seats.trigger('hilite', ids); 130 | }, 131 | 132 | hiliteNone: function() { 133 | this.seats.trigger('hilite', []); 134 | }, 135 | 136 | render: function() { 137 | var available = this.seats.where({status: 'available'}); 138 | var occupied = this.seats.where({status: 'occupied'}); 139 | 140 | var template = _.template($('#dashboard-template').html()); 141 | var html = template({ 142 | num_available: available.length, 143 | num_occupied: occupied.length 144 | }); 145 | this.$el.html(html); 146 | return this; 147 | } 148 | }); 149 | 150 | var SeatDetails = Backbone.View.extend({ 151 | events: { 152 | 'click .seat-reserve': 'reserve' 153 | }, 154 | 155 | initialize: function(options) { 156 | this.seats = options.seats; 157 | 158 | this.listenTo(this.seats, 'selected', this.render); 159 | 160 | this.render(this.seats.selected); 161 | }, 162 | 163 | render: function(id) { 164 | var seat = this.seats.get(id); 165 | if (!seat) { 166 | return; 167 | } 168 | 169 | var template = _.template($('#seat-details-template').html()); 170 | var html = template({ 171 | seat: seat.toJSON() 172 | }); 173 | this.$el.html(html); 174 | return this; 175 | }, 176 | 177 | reserve: function() { 178 | var id = this.seats.selected; 179 | var seat = this.seats.get(id); 180 | if (!seat) { 181 | return; 182 | } 183 | var confirmed = window.confirm("Reserve this seat for the price shown?"); 184 | if (confirmed) { 185 | seat.set('status', 'occupied'); 186 | 187 | // re-render this view 188 | this.render(id); 189 | } 190 | } 191 | }); 192 | 193 | var SeatMapApp = Backbone.View.extend({ 194 | initialize: function(options) { 195 | // create collections 196 | this.collections = { 197 | seats: new SeatCollection(options.seats, {}) 198 | }; 199 | 200 | // initialize views 201 | this.views = { 202 | seatmap: new SeatMap({ 203 | el: this.$('#office')[0], 204 | seats: this.collections.seats 205 | }), 206 | 207 | dashboard: new SeatDashboard({ 208 | el: this.$('#dashboard')[0], 209 | seats: this.collections.seats 210 | }), 211 | 212 | seatdetails: new SeatDetails({ 213 | el: this.$('#seat-details')[0], 214 | seats: this.collections.seats 215 | }) 216 | }; 217 | 218 | } 219 | }); 220 | 221 | $(window).on('load', function() { 222 | var app = new SeatMapApp({ 223 | el: $('body')[0], 224 | seats: SEATS 225 | }); 226 | window.app = app; 227 | }); 228 | 229 | })(jQuery) --------------------------------------------------------------------------------