PageSourceSearch

https://billet.aalborgzoo.dk/assets/event-shop/scripts/Seatmap.class-20885e5a1440531e2418248cab6420c9.js

js aalborgzoo.dk collected 2026-10-02 06:45:14 UTC 46,819 bytes, 1,332 lines download raw bytes

1/* 
2 * Generic class for rendering and using a grafical map of seats in sections of a hall.
3 *
4 * External requrements:
5 *		jQuery v1.7							http://jquery.com/
6 *		jQuery 2d Transform v0.9.3			http://wiki.github.com/heygrady/transform/
7 *		
8 *	Changelog:
9 *		1.2.2
10 *			- Bumped jQuery requirement to 1.7, replacing .delegate() with .on().
11 *			- Added 'context' in this.config.
12 *			- New method modifySelectedSeats(seats, selectSeats).
13 *			- updateSectionMap now respects autoSelectSection
14 *			- Render sections with border radius on section map.
15 *			- Config to render seat number inside the seat div.
16 *		1.2.1
17 *			- Added 'rotateSeats' and 'autoSelectSection' in this.config.
18 *			- Title tag in section now uses title value instead of name value from dataset.
19 *			- Now 'renderSectionMap' takes 'width' and 'height' as parameters
20 *			  which override the width and height specified in hall object.
21 *			  Used for rendering different sizes of the hall overview.
22 *		1.2.0
23 *			- Javascript does not garantee preserve order for associative arrays: for (rowId in rows)
24 *			  While it works in IE and Firefox, Chrome and Opera does not preserve the order.
25 *			  Fix: A new array 'order' must now be supplied for rows and seats,
26 *		1.1.0
27 *			- Added new callback function 'onRenderSectionFinish' which allow application to add extra functionalities to current section.
28 *			- Added extra seat details if provided in data set:
29 *				- seat class label added class attribute, will be concated to title attribute too.
30 *				- order id will be added as data-order_id.
31 *				- customer name as data-customer_name, will be concated to title attribute too.
32 *			- Renamed 'getSeatClass()' to 'getSeatStatusClass()' to avoid misunderstand with the new seat class label detail.
33 *			- A class 'rotated' will be added to sectionDiv element, if angle angle > 90 && angle < 270.
34 *		
35 *	Usage:
36 *		See example/seatmap.html and example/Seatmap.example.js for a quick introduction.
37 *		seatmap = new Seatmap();
38 *		seatmap.init({hall: {see: 'example'}});
39 *		
40 *		You provide a hall definition in the call to init().
41 *		The class renders a section map, where the active sections can be clicked.
42 *		When a sections is selected, Seatmap calls a call back to get data about the rows and seats (unless cached from earlier).
43 *			Each seat has a number and a status.
44 *			More than one status can be mapped to the same CSS class (for styling).
45 *			The Seatmap class never does any AJAX, only calls the call back funtions you define.
46 *		When you supply these data, the seat map is rendered.
47 *		When a seat is clicked, the selected status is toggled. This behaviour can be modified via an call back.
48 *		When a seat is right clicked, you are asked what options to offer (call back).
49 *			- When an option is selected the specified funtion is called.
50 *		
51 *	You must provide:
52 *		Configuration:
53 *			Provide this in the call to init(). See Seatmap.config for commented defaults.
54 *		HTML-elements:
55 *			DIV for the section map (sectionmapDiv) - here the hall image and sections are rendered
56 *			DIV for the seat map (seatmapDiv) - here are all the seats placed inside a div.section-rotator
57 *		CSS styling:
58 *			See example/seatmap.css for examples. Some styles HAVE to be defined!
59 *		Callback functions:
60 *			This is not strictly needed, but for any advanced use it is.
61 *
62 * @author Søren Thing ([email protected])
63 * @copyright 2011 Net Image A/S
64 */
65
66function Seatmap() {
67	// Default configuration - the same format as used when calling init().
68	this.config = {
69		// The IDs of the sections which are active (ie can be selected)
70		// false => all sections are active
71		// [1,2] => section-IDs 1 and 2 are active
72		activeSections:			false,
73
74		// This flag indicates if a section should be selected if no selectedSection is provided.
75		// True => selects the first active section, if no selectedSection is provided
76		// False => does not automatically select a section, only applied if no selectedSection is provided.
77		autoSelectSection:		true,
78
79		// All the callbacks are called in the context of the seatmap instance.
80		// The context variable can be used by the callback functions if they need access to data outside the seatmap.
81		// Never used by the seatmap class.
82		context:				{},
83
84		// ID of the currently selected section if it is unnumbered
85		currentUnnumberedSection: null,
86
87		// Dimensions (in pixels) used when rendering
88		dimSeatSizeX:			10,	// The width of an horse's arse
89		dimSeatSpacingX:		20,	// The spacing of the seats, ie distance from one front-left corner to the next seat's front-left corner.
90		dimSeatSpacingY:		20,
91		dimRowLabelWidth:		50, // Width of row labels
92
93		// This functions is called when a seat is "right mouse button" clicked.
94		// Must return an object {'Label': function() {}, ...} or false (no menu is shown).
95		// Arguments: (sectionId, rowId, seatId, seat, seatData, isSelected) - see example in example/Seatmap.example.js.
96		// The functions will be called with the folowing arguments: sectionId, rowId, seatId, seatData, isSelected.
97		getContextMenuOptions:	false,
98
99		// This function is called when a user selects a section and the section data has been cached.
100		// Arguments: (sectionId)
101		getSeatStatus:			false,
102
103		// The function to call when we need data about a section (ie rows, seats).
104		// The section-ID is supplied as the only parameter.
105		// If false no seatmap can be drawn, but sections can still be selected.
106		// Will be called in the context of the seatmap.
107		// If supplied the function must call setSectionData() like getSectionData() in example/Seatmap.example.js does.
108		getSectionData:			false,
109
110		// Data about the hall - it's name and sections
111		// This parameter is mandatory when calling init().
112		// See an example of a hall object in example/Seatmap.example.js.
113		hall:					false,
114		
115		// The function to call when seat rendering process is done.
116		// Parameters: section (this is the current section jQuery element)
117		// It allows the application to add extra functionalities to the seatmap.
118		// E.g. adding a tooltip on each seat.
119		// See example/Seatmap.example.js.
120		onRenderSectionFinish:	false,
121
122		// The function to call when the user left-clicks on a seat.
123		// If no function is specified the selected state of the seat is toggled.
124		// Parameters: sectionId, rowId, seatId, seat, seatData, isSelected.
125		// See an example in example/Seatmap.example.js.
126		// Must return a boolean, false => no action, true => the selected state of the seat is toggled.
127		onSeatClick:			false,
128		
129		// The function to call when a section is selected in the imagemap for the hall.
130		// The default is this.renderSection.
131		// The return value is ignored.
132		onSelectSection:		false,
133
134		// This function is called when the seatmap is rendered.
135		// It allows the application to resize the container.
136		// Parameters: (seatmapDiv, width_in_px, height_in_px, number_of_seats_in_widest_row, number_of_rows)
137		// See example/Seatmap.example.js.
138		resizeSeatmapDiv:		false,
139
140		// This flag indicates if the seats should be rotated in renderSection().
141		// Null => will always rotate seats if possible, determined by browser.
142		// True => force rotation, false => force no rotation
143		rotateSeats:			null,
144
145		// The ID of the div we should render the seatmap in.
146		seatmapDiv:				'seatmapcontainer',
147		
148		// The ID of the div containing the section map.
149		sectionmapDiv:			'sectionmapcontainer',
150
151		// The IDs of the currently selected seats.
152		// Format: {sectionID: {rowId: [seatId, ...], ...}, ...}
153		// Can also be set with setSelectedSeats()
154		selectedSeats:			{},
155
156		// The ID of the currently selected section.
157		selectedSection:		false,
158
159		// Should the seat number be shown inside the seat div?
160		showSeatName:			false,
161
162		// A text rendered in front of the seats inside a div with class="stagetext".
163		// Set to false to disable the text.
164		stageText:				'Stage',
165
166		// Per default the status of a seat is used directly as a HTML class.
167		// Here you can provide a map, so that your custom rules are applied.
168		// A status mapped to false means that the seat takes up space, but is not shown and cannot be selected.
169		// See an example of a statusMap object in example/Seatmap.example.js.
170		statusMap:				{}
171	};
172
173	/**
174	 * The options in the current seat context menu.
175	 * Format: {'Label': function() {}, ...}
176	 */
177	this.contextMenuOptions = false;
178
179	/**
180	 * Removes event-handlers added by the class.
181	 */
182	this.destroy = function() {
183		// Section map
184		$('div#' + this.config.sectionmapDiv).off();
185		// Seats
186		$('div#' + this.config.seatmapDiv).off();
187		// Context menu
188		$('body')
189			.off('mouseleave', '#seat-context-menu')
190			.off('click', '#seat-context-menu a');
191	} // End destroy()
192
193	/**
194	 * Returns the class mapped to status, or simply status if no map is provided for status.
195	 */
196	this.getSeatStatusClass = function(status) {
197		if (undefined === this.config.statusMap[status]) {
198			return status;
199		}
200		return this.config.statusMap[status];
201	} // End getSeatStatusClass()
202
203
204	/**
205	 * Returns the seat as an object: {number: '', status: ''} or false if unknown.
206	 * 
207	 * @param sectionId
208	 * @param rowId
209	 * @param seatId
210	 * @return {number: '', status: ''} or false
211	 */
212	this.getSeat = function(sectionId, rowId, seatId) {
213		if (		(undefined == this.config.hall.sections[sectionId])
214				||	(undefined == this.config.hall.sections[sectionId].rows)
215				||	(undefined == this.config.hall.sections[sectionId].rows[rowId])
216				||	(undefined == this.config.hall.sections[sectionId].rows[rowId].seats[seatId])) {
217			return false; // Unknown seat
218		}
219		return this.config.hall.sections[sectionId].rows[rowId].seats[seatId];
220	} // End getSeat
221	
222	/**
223	 * Initialization.
224	 * The only mandatory key in config is 'hall'.
225	 * Draws the SectionMap.
226	 *
227	 * @param config			key/value pairs describing the wanted behaviour. See this.config for possible key/values.
228	 * @return boolean			True ved success.
229	 */
230	this.init = function(config) {
231		for (key in config) {
232			this.config[key] = config[key];
233		}
234
235		// Some defaults cannot be supplied above, as they are defined below.
236		if (this.config.onSelectSection == false) {
237			this.config.onSelectSection = this.renderSection;
238		}
239
240		// All functions shall be called in the context of this seatmap instance.
241		for (key in this.config) {
242			if ($.type(this.config[key]) === "function") {
243				this.config[key] = $.proxy(this.config[key], this);
244			}
245		}
246
247		// Verify that a hall is defined
248		if ((undefined == this.config.hall.sections) || ! $.isPlainObject(this.config.hall.sections)) {
249			this.config.hall = false;
250			return false;
251		}
252
253		// Render the SectionMap
254		this.renderSectionMap(this.config.hall);
255
256		// Add click handler for seats - both normal and right-click
257		$('div#' + this.config.seatmapDiv)
258			.on('click contextmenu', '.seat', this, this.onSeatClick);
259		// Event handlers for context menu
260		$('body')
261			.on('mouseleave', '#seat-context-menu', function(e) {
262															$(this).remove();
263														})
264			.on('click', '#seat-context-menu a', this, this.onContextMenuSelect);
265														
266		return true;
267	} // End init()
268
269	/**
270	 * Returns true if the seat is selected.
271	 *
272	 * @param sectionId
273	 * @param rowId
274	 * @param seatId
275	 * @param selectedSeats			Optional - if not supplied this.config.selectedSeats are used.
276	 * @return boolean
277	 */
278	this.isSelected = function(sectionId, rowId, seatId, selectedSeats) {
279		if ( ! selectedSeats) {
280			selectedSeats = this.config.selectedSeats;
281		}
282		var selected = false;
283		if (		(undefined !== selectedSeats[sectionId])
284				&&	(undefined !== selectedSeats[sectionId][rowId])) {
285			// One or more seats are selected in this row
286			for (index in selectedSeats[sectionId][rowId]) {
287				if (selectedSeats[sectionId][rowId][index] == seatId) {
288					selected = true;
289					break;
290				}
291			}
292		}
293		return selected;
294	} // End isSelected()
295
296	/**
297	 * Click handler for seat context menu entries.
298	 */
299	this.onContextMenuSelect = function(e) {
300		// this is the link in the context menu.
301		// e.data is the seatmap object.
302		var link		= $(this);
303		var menu		= $(link).parent();
304		var sectionId	= menu.attr('data-sectionId');
305		var rowId		= menu.attr('data-rowId');
306		var seatId		= menu.attr('data-seatId');
307		var label		= link.text();
308		
309		// Find seat-object and selected state
310		var seatData	= e.data.getSeat(sectionId, rowId, seatId);
311		var isSelected	= e.data.isSelected(sectionId, rowId, seatId);
312		
313		// Remove the context menu
314		menu.remove();
315		
316		// Call the handler
317		if (undefined !== e.data.contextMenuOptions[label]) {
318			e.data.contextMenuOptions[label](sectionId, rowId, seatId, seatData, isSelected);
319		}
320		
321		return false;
322	} // End onContextMenuSelect()
323
324	/**
325	 * Click handler for seats.
326	 * Handles both left and right clicks.
327	 * For right mouse button clicks we call a callback function to get the possible options for the seat.
328	 */
329	this.onSeatClick = function(e) {
330		// this is the seat DOM element (a div)
331		// e is the event normalized by jQuery
332		// e.data is the seatmap object.
333		
334		// Find section/row/seat ID
335		var seat		= $(this);
336		var row			= seat.parent('span');
337		var section		= row.parent('.section-rotator');
338		var seatId		= seat.attr('data-id');
339		var rowId		= row.attr('data-id');
340		var sectionId	= section.attr('data-id');
341		
342		// Find seat-object and selected state
343		var seatData	= e.data.getSeat(sectionId, rowId, seatId);
344		var isSelected	= e.data.isSelected(sectionId, rowId, seatId);
345		
346		switch (e.type) {
347			case 'click':
348				var toggle = true;
349				if (e.data.config.onSeatClick) {
350					toggle = e.data.config.onSeatClick(sectionId, rowId, seatId, seat, seatData, isSelected);
351				}
352				if (toggle) {
353					e.data.toggleSeatSelection(sectionId, rowId, seatId, seat);
354				}
355				return false;
356				break;
357				
358			case 'contextmenu':
359				if ( ! e.data.config.getContextMenuOptions) {
360					return true; // Just show normal contextmenu - no handler is registered.
361				}
362				// Get menu-options
363				e.data.contextMenuOptions = e.data.config.getContextMenuOptions(sectionId, rowId, seatId, seat, seatData, isSelected);
364				if ( ! e.data.contextMenuOptions) {
365					return false; // No menu for this seat.
366				}
367				e.data.renderContextMenu(sectionId, rowId, seatId, e.pageX, e.pageY);
368				return false;
369				break;
370				
371			default:
372				return;
373				break;
374		}
375	} // End onSeatClick()
376	
377	/**
378	 * Renders the context menu for "right mouse button" clicked seats.
379	 *
380	 * @param sectionId
381	 * @param rowId
382	 * @param seatId
383	 * @param x				From event.pageX
384	 * @param y				From event.pageY
385	 */
386	this.renderContextMenu = function(sectionId, rowId, seatId, x, y) {
387		if ( ! this.contextMenuOptions) {
388			return false;
389		}
390
391		var entries = [];
392		for (label in this.contextMenuOptions) {
393			entries.push('<a href="#">' + label + '</a>');
394		}
395
396		// Render the menu at littel above and to the left of the cursor.
397		x = (x < 20) ? 0 : x - 20;
398		y = (y < 20) ? 0 : y - 20;
399		
400		// Create div and insert entries.
401		var div = $('<div id="seat-context-menu"/>');
402		div.
403			css({	left:		x,
404					top:		y
405					})
406			.attr({	'data-sectionId':	sectionId,
407					'data-rowId':		rowId,
408					'data-seatId':		seatId
409					})
410			.html(entries.join(''));
411		
412		// Add to body
413		$('body').append(div);
414		
415		// Set correct height
416		var contentMenuHeight = 0;
417		div.children().each(function() {
418			contentMenuHeight = contentMenuHeight + $(this).innerHeight();
419		});
420		div.css('height', contentMenuHeight +'px');
421	} // End renderContextMenu()
422
423	// Should renderSection() be called when data for the selected section is received?
424	this.renderSectionOnDataReady = false;
425
426	/**
427	 * Renders the grafical/visual seatmap for the specified section.
428	 *
429	 * @param options               Optional. Contains options which is used when rendering section,
430	 *								e.g. no rotation of seats.
431	 */
432	this.renderSection = function(options) {
433		// Is a section selected?
434		if ( ! this.config.selectedSection) {
435			return false;
436		}
437		var sectionId = this.config.selectedSection;
438
439		// Is sectionId OK?
440		if (undefined == this.config.hall.sections[sectionId]) {
441			return false;
442		}
443
444		// Find the div to render the data in.
445		var seatmapDiv = $('div#' + this.config.seatmapDiv);
446		if (seatmapDiv.length == 0) {
447			return false;
448		}
449
450		// Do we have data about rows and seats in the section?
451		if (undefined == this.config.hall.sections[sectionId].rows) {
452			// No, get the data, and then call this function again.
453			seatmapDiv
454				.text('')
455				.addClass('fetching');
456			this.renderSectionOnDataReady = true;
457			if (this.config.getSectionData) {
458				this.config.getSectionData(sectionId);
459			}
460			return false;
461		}
462
463		// OK, we have data
464		this.renderSectionOnDataReady = false;
465		seatmapDiv.removeClass('fetching');
466		var rows = this.config.hall.sections[sectionId].rows;
467
468		// To make correct alignment, find length of longest row (if not already done)
469		if (undefined == this.config.hall.sections[sectionId].maxRowLength) {
470			this.config.hall.sections[sectionId].maxRowLength = 0;
471			this.config.hall.sections[sectionId].numRows = 0;
472			if (undefined != rows.order) {
473				for (key in rows.order) {
474					rowId = rows.order[key];
475					this.config.hall.sections[sectionId].numRows++;
476					row = rows[rowId];
477					// We cannot use row.seats.length as row.seats is not an aray but an object.
478					// Count by iterating and store the result in row.length.
479					row.length = 0;
480					if (undefined != row.seats.order) {
481						for (key in row.seats.order) {
482							row.length++;
483						}
484					}
485					if (row.length > this.config.hall.sections[sectionId].maxRowLength) {
486						this.config.hall.sections[sectionId].maxRowLength = row.length;
487					}
488				}
489			}
490		}
491		var maxLength = this.config.hall.sections[sectionId].maxRowLength;
492		
493		// Only render the map if there are at least one seat
494		if (maxLength == 0) {
495			if (this.config.resizeSeatmapDiv) {
496				this.config.resizeSeatmapDiv(seatmapDiv, 0, 0, 0, 0);
497			}
498			return false;
499		}
500
501		let stageTextPlacement = this.config.hall.sections[sectionId].stageTextPlacement;
502		if (stageTextPlacement === undefined || stageTextPlacement === null || stageTextPlacement === '') {
503			stageTextPlacement = 'front';
504		}
505
506		let rowLabelPlacement = this.config.hall.sections[sectionId].rowLabelPlacement;
507		if (rowLabelPlacement === undefined || rowLabelPlacement === null || rowLabelPlacement === '') {
508			rowLabelPlacement = 'left';
509		}
510
511		// Calculate the size we need to render the seats
512		let neededWidth = (maxLength - 1) * this.config.dimSeatSpacingX + this.config.dimSeatSizeX;
513		if (rowLabelPlacement === 'both') {
514			neededWidth += 2 * this.config.dimRowLabelWidth + (this.config.dimSeatSpacingX - this.config.dimSeatSizeX);
515		}
516		else if (rowLabelPlacement === 'left' || rowLabelPlacement === 'right') {
517			neededWidth += this.config.dimRowLabelWidth;
518		}
519		var neededHeight	= this.config.hall.sections[sectionId].numRows * this.config.dimSeatSpacingY;
520
521		let stageSize = 0;
522		if (this.config.stageText !== false && stageTextPlacement !== 'hidden') {
523			// Fake rendering a stage to find its height
524			let w = (maxLength - 1) * this.config.dimSeatSpacingX + this.config.dimSeatSizeX;
525			const fakeStage = $('<div class="stagetext" style="top: -9999px; left: -9999px; width: ' + w + 'px;">' + this.config.stageText + '</div>');
526			let fakeStageParent = seatmapDiv.closest('.seatmap');
527			if (fakeStageParent.length === 0) {
528				fakeStageParent = 'body';
529			}
530			fakeStage.appendTo(fakeStageParent);
531			stageSize = fakeStage.outerHeight();
532			fakeStage.remove();
533
534			if (stageTextPlacement === 'front' || stageTextPlacement === 'back') {
535				neededHeight += stageSize;
536			}
537			else if (stageTextPlacement === 'left' || stageTextPlacement === 'right') {
538				neededWidth += stageSize;
539				neededHeight = Math.max(neededHeight, 80);
540			}
541		}
542		
543		let rotateSeats = true;
544		let rotateTexts = true;
545		
546		// Overwrite rotateSeats value?
547		if ( this.config.rotateSeats != null ) {
548			rotateSeats = this.config.rotateSeats;
549		}
550		
551		// Provide the application with the size of the rendered seatmap - perhaps the want to resize the container.
552		if (this.config.resizeSeatmapDiv) {
553			var rotatedWidth	= neededWidth;
554			var rotatedHeight	= neededHeight;
555			if (rotateSeats) {
556				var rad = this.config.hall.sections[sectionId].seatDirection * Math.PI / 180;
557				var costheta = Math.cos(rad);
558				var sintheta = Math.sin(rad);
559				rotatedWidth	= Math.round(Math.abs(neededWidth * costheta) + Math.abs(neededHeight * sintheta));
560				rotatedHeight	= Math.round(Math.abs(neededWidth * sintheta) + Math.abs(neededHeight * costheta));
561			}
562			this.config.resizeSeatmapDiv(seatmapDiv, rotatedWidth, rotatedHeight, maxLength, this.config.hall.sections[sectionId].numRows);
563		}
564
565		// Rotate about the center of the div - the compas-direction matches the amount we need to rotate the seats.
566		let angle = this.config.hall.sections[sectionId].seatDirection;
567
568		// Try to center the result in the div
569		let x0 = parseInt((seatmapDiv.width() - neededWidth) / 2, 10);
570		let y0 = parseInt((seatmapDiv.height() - neededHeight) / 2, 10);
571		let yC = parseInt(seatmapDiv.height() / 2, 10);
572		let y = y0;
573		let x = x0;
574
575		// Add text about the stage
576		const rowHtml = [];
577		if (this.config.stageText !== false && stageTextPlacement !== 'hidden') {
578			let r = 0;
579			let c = '';
580			switch (stageTextPlacement) {
581				case 'back':
582					r = 0;
583					if (rotateSeats && rotateTexts) {
584						c = 'rotated';
585						if (angle > 90 && angle < 270) {
586							c = '';
587							r = 180;
588						}
589					}
590					else {
591						c = 'rotated';
592						r = 0;
593					}
594					if (rowLabelPlacement === 'left' || rowLabelPlacement === 'both') {
595						x += this.config.dimRowLabelWidth;
596					}
597					w = (maxLength - 1) * this.config.dimSeatSpacingX + this.config.dimSeatSizeX;
598					y = y0 + this.config.hall.sections[sectionId].numRows * this.config.dimSeatSpacingY;
599					break;
600				case 'left':
601					r = 270;
602					w = neededHeight;
603					y = yC - this.config.dimSeatSpacingX;
604					x = x0 - this.config.dimSeatSpacingX * 2;
605					break;
606				case 'right':
607					r = 90;
608					w = neededHeight;
609					y = yC - this.config.dimSeatSpacingX;
610					x = (maxLength - 1) * this.config.dimSeatSpacingX + this.config.dimSeatSizeX;
611					if (rowLabelPlacement === 'left' || rowLabelPlacement === 'right') {
612						x += this.config.dimRowLabelWidth;
613					}
614					else if (rowLabelPlacement === 'both') {
615						x += this.config.dimRowLabelWidth * 2;
616					}
617					break;
618				case 'front':
619				default:
620					if (rotateSeats && rotateTexts) {
621						if (angle > 90 && angle < 270) {
622							c = 'rotated';
623							r = 180;
624						}
625					}
626					x = x0;
627					if (rowLabelPlacement === 'left' || rowLabelPlacement === 'both') {
628						x += this.config.dimRowLabelWidth;
629					}
630					w = (maxLength - 1) * this.config.dimSeatSpacingX + this.config.dimSeatSizeX;
631					break;
632			}
633
634			rowHtml.push('<div class="stagetext ' + c + '" style="top: ' + y + 'px; left: ' + x + 'px; width: ' + w + 'px; transform: rotate(' + r + 'deg);">' + this.config.stageText + '</div>');
635			if (stageTextPlacement === 'front') {
636				y += stageSize + this.config.dimSeatSpacingY - this.config.dimSeatSizeX;
637			}
638			else if (stageTextPlacement === 'back' || stageTextPlacement === 'right') {
639				y = y0;
640			}
641			else if (stageTextPlacement === 'left') {
642				x0 += stageSize;
643				y = y0;
644			}
645		}
646
647		// Iterate over the rows
648		for (key in rows.order) {
649			rowId = rows.order[key];
650			row = rows[rowId];
651			
652			// Find X-coordinate of first seat
653			x = x0;
654			if (rowLabelPlacement === 'left' || rowLabelPlacement === 'both') {
655				x += this.config.dimRowLabelWidth;
656			}
657
658			switch (row.seatAlignment) {
659				case 'right':
660					x += (maxLength - row.length) * this.config.dimSeatSpacingX;
661					break;
662
663				case 'center':
664					x += parseInt((maxLength - row.length) / 2 * this.config.dimSeatSpacingX, 10);
665					break;
666
667				case 'left':
668				default:
669					break;
670			}
671
672			// Iterate over the seats
673			seatHtml = [];
674			x -= this.config.dimSeatSpacingX;
675			if (undefined != row.seats.order) {
676				for (key in row.seats.order) {
677					seatId = row.seats.order[key];
678					seat = row.seats[seatId];
679					x += this.config.dimSeatSpacingX;
680					// Skip seat?
681					seatClass = this.getSeatStatusClass(seat.status);
682					if (seatClass === false) {
683						continue;
684					}
685
686					// Extra attributes to seat element
687					seatClassLabel	= '';
688					extraData		= '';
689					seatTitle		= seat.number;
690					var seatAltClassLabel = '';
691					var seatName	= '';
692					if (this.config.showSeatName
693							&& seat.status !== 'missing' && seat.status !== 'blocked') {
694						seatName = seat.number;
695					}
696
697					// If the seat has a class label, add it to the seat element
698					if ( seat.classLabel != undefined ) {
699						seatClassLabel = ' ' + seat.classLabel;
700						// If seat class label has a name, we add it to the title tag
701						if ( this.config.hall.seatClasses[seat.classLabel] != undefined
702							&& this.config.hall.seatClasses[seat.classLabel].name != undefined ) {
703							seatTitle += ' (' + this.config.hall.seatClasses[seat.classLabel].name + ')';
704						}
705					}
706
707					// An order_id exists?
708					extraData += (seat.orderId != undefined ? ' data-order_id="' + seat.orderId + '"' : '');
709
710					// Customer name?
711					if ( seat.customerName != undefined ) {
712						extraData += ' data-customer_name="' + seat.customerName + '"';
713
714						// add customer name to title
715						seatTitle += ' - ' + seat.customerName;
716					}
717					
718					// Customer company?
719					if ( seat.customerCompany != undefined && seat.customerCompany !== '') {
720						// add customer company to title
721						seatTitle += ' - ' + seat.customerCompany;
722					}
723
724					if (seat.altClassLabel !== undefined) {
725						seatAltClassLabel += ' ' + seat.altClassLabel;
726					}
727
728					// Is the seat selected?
729					selected = this.isSelected(sectionId, rowId, seatId) ? ' selected' : '';
730					// Now create the seat as HTML
731					seatHtml.push('<div class="seat ' + seatClass + seatClassLabel + seatAltClassLabel + selected
732										+'" style="top: ' + y + 'px; left: ' + x + 'px;" title="' + seatTitle + '" data-id="' + seatId + '"' + extraData + '>' + seatName + '</div>');
733				}
734			}
735
736			// Collect the seats of a row in a span
737			rowHtml.push('<span data-id="' + rowId + '">');
738			if (rowLabelPlacement === 'left' || rowLabelPlacement === 'both') {
739				rowHtml.push('<div class="seatrow" style="top: ' + y + 'px; left: ' + x0 + 'px;">' + row.name + '</div>');
740			}
741			rowHtml.push(seatHtml.join(''));
742			if (rowLabelPlacement === 'right' || rowLabelPlacement === 'both') {
743				switch (row.seatAlignment) {
744					case 'right':
745						break;
746					case 'center':
747						x += Math.floor((maxLength - row.length) / 2 * this.config.dimSeatSpacingX);
748						break;
749					case 'left':
750					default:
751						x += (maxLength - row.length) * this.config.dimSeatSpacingX;
752						break;
753				}
754				x += this.config.dimSeatSpacingX;
755				rowHtml.push('<div class="seatrow" style="top: ' + y + 'px; left: ' + x + 'px; width: ' + this.config.dimRowLabelWidth + 'px; text-align: right;">' + row.name + '</div>');
756			}
757			rowHtml.push('</span>');
758
759
760			// Prepare for next row
761			x = x0;
762			y += this.config.dimSeatSpacingY;
763		}
764
765		// Now insert all the rows in a div that we can rotate
766		seatmapDiv
767			.html('<div class="section-rotator" data-id="' + sectionId + '">'
768					+ rowHtml.join('')
769					+ '</div>');
770				
771		var currentSection = seatmapDiv.find('.section-rotator');
772
773		if (rotateSeats) {
774			currentSection.css('transform', 'rotate(' + angle + 'deg)')
775				.addClass((angle > 90 && angle < 270 ? 'rotated' : '')); // adding rotated class for seat class icon usage
776			
777			if (rotateTexts) {
778				// Hvis rotationen er mellem 90 og 270 grader, så vend teksterne på hovedet (undtagen stage text da det allerede er håndteret).
779				if (angle > 90 && angle < 270) {
780					seatmapDiv
781						.find('div.seatrow, div.seat')
782						.css('transform', 'rotate(180deg)')
783						.addClass('rotated');
784				}
785			}
786		}
787		
788		// Finalizing callback
789		if ( typeof this.config.onRenderSectionFinish == 'function' ) {
790			this.config.onRenderSectionFinish(currentSection);
791		}
792		
793	} // End renderSection()
794	
795	
796	/**
797	 * Renders the section map.
798	 * 
799	 * @param hall	{name: 'name', image: 'url', imageWidth: int, imageHeight: int, sections: {sectionID: {rowId: {seatId: 'status', ...}, ...}, ...}}
799
800	 * @param width		Will overwrite the width specified in hall object.
801	 * @param height	Will overwrite the height specified in hall object.
802	 */
803	this.renderSectionMap = function(hall, width, height) {
804		var $container = $('#' + this.config.sectionmapDiv);
805		
806		if ( $container.length == 0 ) {
807			return false;
808		}
809		
810		if ( hall.sections.length == 1 ) {
811			return false;
812		}
813		
814		var useWidth	= ( width ? width : hall.imageWidth );
815		var useHeight	= ( height ? height : hall.imageHeight );
816
817		// Adding the hall image
818		var image = $('<img>').attr({src: hall.image, alt: '', zIndex: 0});
819		$container.append(image);
820		$container.css({width: useWidth});
821		$container.css({height: useHeight});
822		
823		var containerWidth = $container.width();
824		var containerHeight = $container.height();
825		var contentHTML = '';
826		
827		if (hall.useSvg) {
828			contentHTML += hall.svg;
829		}
830
831		// Runs through each section
832		jQuery.each(hall.sections, $.proxy(function(sectionId, data) {
833						
834			var height		= containerHeight * (data.ymax - data.ymin);
835			var width		= containerWidth * (data.xmax - data.xmin);
836			var top			= containerHeight * data.ymin;
837			var left		= containerWidth * data.xmin;
838			var isActive	= false;
839			var className	= 'sectionmap';
840
841			// If no active sections is defined, all sections are avaiable.
842			if ( this.config.activeSections === false ) {
843				isActive = true;
844			}
845			else {
846				// We check if the current section id is among the active ones.
847				for (i in this.config.activeSections) {
848					if ( this.config.activeSections[i] == sectionId ) {
849						isActive = true;
850						break;
851					}
852				}	
853			}
854			
855			// If we have no selected section yet, we select the first *active* one
856			if ( this.config.autoSelectSection && isActive && this.config.selectedSection == false ) {
857				this.config.selectedSection = sectionId;
858			}
859
860			className += (isActive ? ' active' : ' pe-none');
861
862			if (data.svg) {
863				contentHTML += '<g ';
864				contentHTML += 'id="section-' + sectionId + '" ';
865				contentHTML += 'data-section_id="' + sectionId + '" ';
866				contentHTML += 'class="' + className + '" ';
867				contentHTML += '>';
868				contentHTML += '<title>';
869				contentHTML += data.title;
870				contentHTML += '</title>';
871				contentHTML += data.svg;
872				contentHTML += '</g>';
873			}
874			else {
875				contentHTML += '<div ';
876				contentHTML += 'id="section-' + sectionId + '" ';
877				contentHTML += 'data-section_id="' + sectionId + '" ';
878				contentHTML += 'title="' + data.title + '" ';
879				contentHTML += 'class="' + className + '" ';
880				contentHTML += 'style="height: ' + height + 'px; width: ' + width + 'px; top: ' + top + 'px; left: ' + left + 'px;';
881				if (data.borderRadiusTopLeft !== undefined && data.borderRadiusTopLeft !== null && data.borderRadiusTopLeft !== '') {
882					contentHTML += ' border-top-left-radius: ' + data.borderRadiusTopLeft + ';';
883				}
884				if (data.borderRadiusTopRight !== undefined && data.borderRadiusTopRight !== null && data.borderRadiusTopRight !== '') {
885					contentHTML += ' border-top-right-radius: ' + data.borderRadiusTopRight + ';';
886				}
887				if (data.borderRadiusBottomLeft !== undefined && data.borderRadiusBottomLeft !== null && data.borderRadiusBottomLeft !== '') {
888					contentHTML += ' border-bottom-left-radius: ' + data.borderRadiusBottomLeft + ';';
889				}
890				if (data.borderRadiusBottomRight !== undefined && data.borderRadiusBottomRight !== null && data.borderRadiusBottomRight !== '') {
891					contentHTML += ' border-bottom-right-radius: ' + data.borderRadiusBottomRight + ';';
892				}
893				contentHTML += '"></div>';
894			}
895		}, this));
896		if (hall.useSvg) {
897			contentHTML += '</svg>';
898		}
899
900		$container.append(contentHTML);
901
902		// Attaching click event
903		var clickElement = 'div';
904		if (hall.useSvg) {
905			clickElement = 'g';
906		}
907		$container.on('click', clickElement + '.active', $.proxy(function(event) {
908			if (hall.useSvg) {
909				this.selectSection($(event.target).closest('g[data-section_id]').attr('data-section_id'));
910			}
911			else {
912				this.selectSection($(event.target).attr('data-section_id'));
913			}
914		}, this));
915
916		// Trigger the selectSection()
917		this.selectSection(this.config.selectedSection);
918		
919	}; // End renderSectionMap
920	
921
922	/**
923	 * Called when a section is selected by clicking on the SectionMap.
924	 * Can also be called manually if no section map is needed or wanted.
925	 *
926	 * @param sectionId		The selected section's ID.
927	 */
928	this.selectSection = function(sectionId) {
929		// If there is no activeSections defined, we skip the check
930		if ( this.config.activeSections !== false ) {
931			// We check if the section id exists in the active section list.
932			var inArray = false;
933			for (i in this.config.activeSections) {
934				if ( this.config.activeSections[i] == sectionId ) {		
935					inArray = true;
936					break;
937				}
938			}
939
940			// Not in array? - We quit!
941			if ( ! inArray ) {
942				return false;
943			}
944		}
945		
946		// Valid sectionId?
947		if (undefined == this.config.hall.sections || undefined == this.config.hall.sections[sectionId]) {
948			return false;
949		}
950
951		this.config.selectedSection = sectionId;	// Set the selected section
952		this.selectSectionMap(sectionId);			// Select visually
953		
954		// Getting status if data in the section has been cached.
955		if ( undefined !== this.config.hall.sections[sectionId].rows ) {
956			if ( this.config.getSeatStatus ) {
957				this.config.getSeatStatus(sectionId);
958			}
959		}
960		
961		this.config.onSelectSection();				// Callback
962	} // End selectSection()
963
964	/**
965	 * Selecting the selected section visually in the sectionmap.
966	 * 
967	 * @param {Number} sectionId		The selected section's ID
968	 */
969	this.selectSectionMap = function(sectionId) {
970		var sectionmapContainerType = 'div';
971		if (this.config.hall.useSvg) {
972			sectionmapContainerType = 'g';
973		}
974		var $sectionMapDiv = $('#' + this.config.sectionmapDiv);
975		if ($sectionMapDiv.length === 0) {
976			return;
977		}
978		var $selectedSectionDiv = $sectionMapDiv.find(sectionmapContainerType + '#section-' + sectionId);
979		if ( ! this.classStringContains($selectedSectionDiv.attr('class'), 'selected')) {
980			// Removing the selected class from old selected section
981			var $oldSelectedSectionDiv = $sectionMapDiv.find(sectionmapContainerType + '.sectionmap.selected');
982			$oldSelectedSectionDiv.attr('class', this.editClassString($oldSelectedSectionDiv.attr('class'), [], ['selected']));
983			$selectedSectionDiv.attr('class', this.editClassString($selectedSectionDiv.attr('class'), ['selected'], []));
984		}
985	}; // End selectSectionMap()
986
987	/**
988	 * Set the IDs of the sections that are active - like activeSections in init().
989	 */
990	this.setActiveSections = function(sectionIds) {
991		this.config.activeSections = sectionIds;
992		// Update SectionMap
993		this.updateSectionMap();
994	} // End setActiveSections()
995
996	/**
997	 * Set data about a section - rows, seats, status.
998	 * See getSectionData() in example/Seatmap.example.js for an example of rows.
999	 * If sectionID is the currently selected section and this.renderSectionOnDataReady is true, renderSection() is called.
1000	 *
1001	 * @param sectionId		The section's ID.
1002	 * @param rows			{rowId: {name: '', seatAlignment: '', seats: {seatId: {number: '', status: ''}, ...}}
1002, ...}
1003	 */
1004	this.setSectionData = function(sectionId, rows) {
1005		// Is sectionId OK?
1006		if (undefined == this.config.hall.sections[sectionId]) {
1007			return false;
1008		}
1009
1010		// Save the data.
1011		this.config.hall.sections[sectionId].rows = rows;
1012
1013		// Render the section?
1014		if (this.config.selectedSection == sectionId && this.renderSectionOnDataReady) {
1015			this.renderSection();
1016		}
1017		return true;
1018	} // End setSectionData()
1019
1020
1021	/**
1022	 * Updates the set of selected seats.
1023	 * A subset of seats can be given.
1024	 * Unknown seats are ignored for now, but stored in case we later learn about them (eg a new section is loaded).
1025	 * All seats *not* specified are deselected.
1026	 * See an example of a selectedSeats object in example/Seatmap.example.js.
1027	 *
1028	 * @param seatStatus	{sectionID: {rowId: [seatId, ...], ...}, ...}
1029	 */
1030	this.setSelectedSeats = function(selectedSeats) {
1031		var rotatorDiv = $('div#' + this.config.seatmapDiv + ' .section-rotator');
1032		var rowSpan = false;
1033
1034		// Visually deselect the seats that are no longer in the set.
1035		sectionId = this.config.selectedSection; // Only the selected section has visible seats
1036		if (		(undefined !== this.config.selectedSeats[sectionId])
1037				&&	(undefined !== this.config.hall.sections[sectionId])
1038				&&	(undefined !== this.config.hall.sections[sectionId].rows)) {
1039			for (rowId in this.config.selectedSeats[sectionId]) {
1040				if (undefined == this.config.hall.sections[sectionId].rows[rowId]) {
1041					continue;
1042				}
1043				rowSpan = false;
1044				for (index in this.config.selectedSeats[sectionId][rowId]) {
1045					seatId = this.config.selectedSeats[sectionId][rowId][index];
1046					if (undefined == this.config.hall.sections[sectionId].rows[rowId].seats[seatId]) {
1047						continue;
1048					}
1049					// OK, it is a seat we know about in the currently selected section.
1050					// Is the seat still selected?
1051					if (this.isSelected(sectionId, rowId, seatId, selectedSeats)) {
1052						continue;
1053					}
1054					// Now remove class 'selected' from the seat
1055					if ( ! rowSpan) {
1056						rowSpan = rotatorDiv.children('span[data-id="' + rowId + '"]');
1057					}
1058					rowSpan
1059						.children('.seat[data-id="' + seatId + '"]')
1060						.removeClass('selected');
1061				}
1062			}
1063		}
1064		
1065		// Visually select the seats that are now selected
1066		sectionId = this.config.selectedSection; // Only the selected section has visible seats
1067		if (		(undefined !== selectedSeats[sectionId])
1068				&&	(undefined !== this.config.hall.sections[sectionId])
1069				&&	(undefined !== this.config.hall.sections[sectionId].rows)) {
1070			for (rowId in selectedSeats[sectionId]) {
1071				if (undefined == this.config.hall.sections[sectionId].rows[rowId]) {
1072					continue;
1073				}
1074				rowSpan = false;
1075				for (index in selectedSeats[sectionId][rowId]) {
1076					seatId = selectedSeats[sectionId][rowId][index];
1077					if (undefined == this.config.hall.sections[sectionId].rows[rowId].seats[seatId]) {
1078						continue;
1079					}
1080					// OK, it is a seat we know about in the currently selected section.
1081					// Was it previously selected?
1082					if (this.isSelected(sectionId, rowId, seatId)) {
1083						continue;
1084					}
1085					// Now add class 'selected' to the seat
1086					if ( ! rowSpan) {
1087						rowSpan = rotatorDiv.children('span[data-id="' + rowId + '"]');
1088					}
1089					rowSpan
1090						.children('.seat[data-id="' + seatId + '"]')
1091						.addClass('selected');
1092				}
1093			}
1094		}
1095		
1096		// Update the list
1097		this.config.selectedSeats = selectedSeats;
1098	} // End setSelectedSeats()
1099
1100	/**
1101	 * Toggles the selected state of a seat.
1102	 *
1103	 * @param sectionId
1104	 * @param rowId
1105	 * @param seatId
1106	 * @param seat			The seat DIV as a jQuery object. Optional.
1107	 */
1108	this.toggleSeatSelection = function(sectionId, rowId, seatId, seat) {
1109		if (this.isSelected(sectionId, rowId, seatId)) {
1110			if (seat) { // Remove class selected
1111				seat.removeClass('selected');
1112			}
1113			// Remove the seatId from the list of selected seats.
1114			for (index in this.config.selectedSeats[sectionId][rowId]) {
1115				if (this.config.selectedSeats[sectionId][rowId][index] == seatId) {
1116					this.config.selectedSeats[sectionId][rowId].splice(index, 1);
1117					break;
1118				}
1119			}
1120		}
1121		else {
1122			if (seat) { // Add class selected
1123				seat.addClass('selected');
1124			}
1125			// Add the seatId to the list of selected seats.
1126			if (undefined == this.config.selectedSeats[sectionId]) {
1127				this.config.selectedSeats[sectionId] = {};
1128			}
1129			if (undefined == this.config.selectedSeats[sectionId][rowId]) {
1130				this.config.selectedSeats[sectionId][rowId] = [];
1131			}
1132			this.config.selectedSeats[sectionId][rowId].push(seatId);
1133		}
1134	} // End toggleSeatSelection()
1135
1136	/**
1137	 * Updates the set of selected seats.
1138	 * Only the specified seats are updated - all other selected seats are kept.
1139	 *
1140	 * @param object seats			{sectionID: {rowId: [seatId, ...], ...}, ...}
1141	 * @param boolean selectSeats	True: The seats are selected. False: The seats are deselected.
1142	 * @returns null
1143	 */
1144	this.modifySelectedSeats = function(seats, selectSeats) {
1145		var rotatorDiv = $('div#' + this.config.seatmapDiv + ' .section-rotator');
1146		var rowSpan = false;
1147		var seatId;
1148		var isSelected;
1149		var seat;
1150		
1151		for (var sectionId in seats) {
1152			for (var rowId in seats[sectionId]) {
1153				rowSpan = false;
1154				for (var index in seats[sectionId][rowId]) {
1155					seatId = seats[sectionId][rowId][index];
1156					isSelected = this.isSelected(sectionId, rowId, seatId);
1157					if (selectSeats != isSelected) {
1158						// This seats selection status needs to be toggled.
1159						// Also update visually if the seat is in the current section.
1160						$seat = null;
1161						if (sectionId == this.config.selectedSection) {
1162							if ( ! rowSpan) {
1163								rowSpan = rotatorDiv.children('span[data-id="' + rowId + '"]');
1164							}
1165							seat = rowSpan.children('.seat[data-id="' + seatId + '"]')
1166						}
1167						this.toggleSeatSelection(sectionId, rowId, seatId, seat);
1168					}
1169				}
1170			}
1171		}
1172	} // End modifySelectedSeats()
1173
1174	/**
1175	 * Updates the status-field of the specified seats.
1176	 * A subset of seats can be given.
1177	 * Unknown seats are ignored.
1178	 * See an example of a seatStatus object in example/Seatmap.example.js.
1179	 *
1180	 * @param seatStatus	{sectionID: {rowId: {seatId: 'status', ...}, ...}, ...}
1181	 */
1182	this.updateSeatStatus = function(seatStatus) {
1183		var rotatorDiv = $('div#' + this.config.seatmapDiv + ' .section-rotator');
1184		var rowSpan = false;
1185		for (sectionId in seatStatus) {
1186			if (		(undefined == this.config.hall.sections[sectionId])
1187					||	(undefined == this.config.hall.sections[sectionId].rows)) {
1188				continue;
1189			}
1190			for (rowId in seatStatus[sectionId]) {
1191				if (undefined == this.config.hall.sections[sectionId].rows[rowId]) {
1192					continue;
1193				}
1194				rowSpan = false;
1195				for (seatId in seatStatus[sectionId][rowId]) {
1196					if (undefined == this.config.hall.sections[sectionId].rows[rowId].seats[seatId]) {
1197						continue;
1198					}
1199					if (this.config.hall.sections[sectionId].rows[rowId].seats[seatId].status != seatStatus[sectionId][rowId][seatId]) {
1200						// Update seat class if needed.
1201						if ((this.config.selectedSection == sectionId)
1202								&& rotatorDiv.length) {
1203							oldClass = this.getSeatStatusClass(this.config.hall.sections[sectionId].rows[rowId].seats[seatId].status);
1204							newClass = this.getSeatStatusClass(seatStatus[sectionId][rowId][seatId]);
1205							if (oldClass != newClass) {
1206								if ( ! rowSpan) {
1207									rowSpan = rotatorDiv.children('span[data-id="' + rowId + '"]');
1208								}
1209								rowSpan
1210									.children('.seat[data-id="' + seatId + '"]')
1211									.removeClass(oldClass)
1212									.addClass(newClass);
1213							}
1214						}
1215						this.config.hall.sections[sectionId].rows[rowId].seats[seatId].status = seatStatus[sectionId][rowId][seatId];
1216					}
1217				}
1218			}
1219		}
1220	} // End updateSeatStatus()
1221
1222	/**
1223	 * Updating the sectionmap with active sections.
1224	 * If active section list is false, all sections will be marked as active.
1225	 */
1226	this.updateSectionMap = function() {
1227		var sectionmapContainerType = 'div';
1228		if (this.config.hall.useSvg) {
1229			sectionmapContainerType = 'g';
1230		}
1231
1232		var self = this;
1233
1234		// We make change to the section map, so we clear all elements with class 'active'.
1235		// If the element is a svg we cant use addClass()/removeClass()
1236		var $sectionmapActive = $('div#' + this.config.sectionmapDiv + ' ' + sectionmapContainerType + '.sectionmap.active');
1237		$sectionmapActive.each(function() {
1238			$(this).attr('class', self.editClassString($(this).attr('class'), ['pe-none'], ['active']));
1239		});
1240
1241		// If no active section is defined, all sections will be active.
1242		if ( ! this.config.activeSections ) {
1243			var $sectionmap = $('div#' + this.config.sectionmapDiv + ' ' + sectionmapContainerType + '.sectionmap');
1244			$sectionmap.each(function() {
1245				$(this).attr('class', self.editClassString($(this).attr('class'), ['active'], ['pe-none']));
1246			});
1247		}
1248		else {
1249			// Looping through all active sections
1250			var changeSelectedSectionMap = true;
1251			for (var i in this.config.activeSections) {
1252				// We check if the current selected is in the active list.
1253				if ( this.config.selectedSection === this.config.activeSections[i] ) {
1254					changeSelectedSectionMap = false;
1255				}
1256				var $activeSection = $('div#' + this.config.sectionmapDiv + ' ' + sectionmapContainerType + '#section-' + this.config.activeSections[i]);
1257				$activeSection.attr('class', this.editClassString($activeSection.attr('class'), ['active'], ['pe-none']));
1258			}
1259
1260			// If the selected section is not in the active list,
1261			// we take the first in the list and select it visually.
1262
1263			if ( changeSelectedSectionMap && this.config.autoSelectSection) {
1264				this.selectSection(this.config.activeSections[0]);
1265			}
1266		}
1267	};
1268
1269	/**
1270	 * Add and/or remove classes to/from a string of classes.
1271	 *
1272	 * @param {String} classes
1273	 * @param {Array} classesToAdd		Array of classes that should be added
1274	 * @param {Array} classesToRemove	Array of classes that should be removed
1275	 * @returns {string}
1276	 */
1277	this.editClassString = function(classes, classesToAdd, classesToRemove) {
1278		if (classes === undefined) {
1279			classes = '';
1280		}
1281		var classesAsArray = classes.split(' ');
1282
1283		var classIndex;
1284		if (classesToAdd) {
1285			for (classIndex in classesToAdd) {
1286				classesAsArray.push(classesToAdd[classIndex]);
1287			}
1288		}
1289
1290		if (classesToRemove) {
1291			for (classIndex in classesToRemove) {
1292				var classToRemove = classesToRemove[classIndex];
1293				var indexToRemove;
1294				while ((indexToRemove = classesAsArray.indexOf(classToRemove)) !== -1) {
1295					classesAsArray.splice(indexToRemove, 1);
1296				}
1297			}
1298		}
1299
1300		return classesAsArray.join(' ');
1301	}; // End editClassString()
1302
1303	/**
1304	 * Check if a class string contains a class.
1305	 *
1306	 * @param {String} classes
1307	 * @param {String} classToCheck
1308	 * @returns {boolean}
1309	 */
1310	this.classStringContains = function(classes, classToCheck) {
1311		return classes.split(' ').indexOf(classToCheck) !== -1;
1312	}; // End classStringContains()
1313
1314	/**
1315	 * Show or hide the alternative seat class colors.
1316	 * @param {Boolean} show	If not defined it will be toggled.
1317	 * @returns {Boolean}	True if shown, false if hidden.
1318	 */
1319	this.toggleAltSeatClass = function(show) {
1320		var $seatmapDiv = $('div#' + this.config.seatmapDiv);
1321		if (show === undefined) {
1322			show = ! $seatmapDiv.hasClass('alt_seat_classes');
1323		}
1324		if (show) {
1325			$seatmapDiv.addClass('alt_seat_classes');
1326		}
1327		else {
1328			$seatmapDiv.removeClass('alt_seat_classes');
1329		}
1330		return show;
1331	};
1332}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.