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.