├── _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 |
--------------------------------------------------------------------------------
/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)
--------------------------------------------------------------------------------