1; 2(function ($, window, document, undefined) { 3 "use strict"; 4 var pluginName = "amReserveBooker", 5 defaults = { 6 apiServer: false, 7 client: false, 8 mode: 0, 9 showSMSPrompt: false, 10 SMSOptMode: 0, 11 SMSPromptMessage: '', 12 offlineButtonText: 'Offline', 13 sessionId: '', 14 chargingMode: 0, 15 mediated: 0, 16 testMode: 0, 17 payment_currency: "USD", 18 payment_currency_symbol: '$', 19 enableStaffNotes: false 20 }; 21 // The actual plugin constructor 22 function Plugin(element, options) { 23 this.element = element; 24 this.$element = $(element); 25 this.settings = $.extend(true, {}, defaults, options); 26 this._defaults = defaults; 27 this._name = pluginName; 28 this.bookings = []; 29 this.currentBooking = false; 30 this.bookingPaymentType = 'card'; 31 this.cardForm = null; 32 this.paypalAmount = 0.00; 33 this.PayPalPayFlow = null; 34 this.availablePaymentTypes = []; 35 36 this.init(); 37 } 38 $.extend(true, Plugin.prototype, $.fn['amShared']); 39 $.extend(Plugin.prototype, { 40 init: function () { 41 var self = this; 42 var defaultPaymentType = 43 self.settings.ecommerceEnabled && self.settings.cardPaymentEnabled && self.settings.mode !== 1 ? 'card' : 'offline'; 44 self.bookingPaymentType = defaultPaymentType; 45 this.settings.bookings.forEach(function (booking) { 46 self.bookings.push({ 47 data: { 48 start_time: booking.start, 49 end_time: booking.end, 50 contact: { first_name: "", last_name: "", phone: "", email: "", librarycard: "", booking_title: "", group_name: "" }, 51 assets: booking.assets, 52 room_id: booking.room.id, 53 expected_attendees: 0, 54 staff_notes: '', 55 customQuestions: {}, 56 layout_id: booking.layout ? booking.layout.id : false, 57 paymentType: defaultPaymentType, 58 bookingClass: booking.booking_class 59 }, 60 layout: booking.layout, 61 room: booking.room, 62 location: booking.location, 63 startTime: moment(booking.start), 64 endTime: moment(booking.end) 65 }); 66 67 if (booking.room.restrictions.require_login) { 68 if (parseInt(booking.room.restrictions.require_login) === 1 && !self.settings.loginRequired) { 69 self.settings.loginRequired = true; 70 } 71 } 72 }); 73 74 this._buildInterface(); 75 this._bindEvents(); 76 77 window.scroll({ 78 top: 0, 79 left: 0 80 }); 81 82 if (this.settings.mode === 1) { 83 this.cardlookup.show(); 84 } else { 85 if (this.settings.loginRequired) { 86 this.loginForm.show(); 87 this.detailsForm.hide(); 88 } 89 } 90 91 }, 92 _buildInterface: function () { 93 var self = this; 94 95 self.holder = $('<div class="amnp-booker-holder"></div>').appendTo(self.element); 96 self.reserveScreen = $('<div class="amnp-reserve-screen amnp-reserve-screen-basket"></div>').appendTo(self.holder); 97 self.reserveScreenShared = $('<div class="amnp-reserve-screen-shared"></div>').appendTo(self.reserveScreen); 98 self.confirmScreen = $('<div class="amnp-confirm-screen"></div>').appendTo(self.holder).hide(); 99 self.screenThankyou = $('<div class="amnp-thanks-screen"></div>').appendTo(self.holder).hide(); 100 101 if (self.bookings.length == 1) { 102 self._getBookingHeader(self.bookings[0]).appendTo(self.reserveScreenShared); 103 } 104 105 if (self.settings.mode === 1) { 106 var bookingType = self._createField('booking_type', 'select', 'Booking type', false).appendTo(self.reserveScreenShared); 107 var bookingTypeSelect = $('select', bookingType); 108 self._getJSON('/ajax/fetch/get_user_classes').then(function (json) { 109 json.forEach(function (cls) { 110 $('<option value="' + cls.id + '">' + cls.name + '</option>').data('details', cls).appendTo(bookingTypeSelect); 111 if (!self.settings.bookingClass && parseInt(cls.type, 10) === 1) { 112 $('#booking_type', self.reserveScreenShared).val(cls.id).trigger('change'); 113 } 114 }); 115 if (self.settings.bookingClass) $('#booking_type', self.reserveScreenShared).val(self.settings.bookingClass).trigger('change'); 116 }).catch(function (err) { 117 console.log(err); 118 }); 119 self.cardlookup = self._createField('login_card', 'text', 'Library card number', false, '<span data-loading-text="<i class=\'fa fa-circle-o-notch fa-spin\'></i>" class="btn btn-edit events2-lookup input-group-addon" tabindex="0" role="button" aria-label="Find details">Find details</span>').addClass('events2-card').appendTo(self.reserveScreenShared).hide(); 120 } else { 121 /*start login form*/ 122 self.loginForm = $('<div class="amnp-login-form"></div>').appendTo(self.reserveScreenShared); 123 if (self.settings.pinlessAuth) { 124 if (self.settings.loginRequired) { 125 self._createField('login_card', 'text', 'Library card number', false, '<span data-loading-text="<i class=\'fa fa-circle-o-notch fa-spin\'></i>" class="btn btn-edit amnp-lookup input-group-addon" tabindex="0" role="button" aria-label="Login">Login</span>', 'A library card is required to book a room.') 126 .appendTo(self.loginForm); 127 } 128 } else { 129 if(self.settings.ezproxy.showLoginButton == 1) { 130 $('<a href="' + self.settings.ezproxy.loginButtonLink + '" class="btn btn-info">' + self.settings.ezproxy.loginButtonText + '</a>').appendTo(self.loginForm); 131 } else { 132 self._createField('login_card', 'text', 'Library card number', false).addClass('amnp-card').appendTo(self.loginForm); 133 if (self.settings.loginRequired) { 134 var pin = self._createField('login_pin', 'password', 'PIN / Password', false, '<span data-loading-text="<i class=\'fa fa-circle-o-notch fa-spin\'></i>" class="btn btn-edit amnp-lookup input-group-addon" tabindex="0" role="button" aria-label="Login">Login</span>', 'A library card is required to book a room.') 135 .appendTo(self.loginForm); 136 } else { 137 var pin = self._createField('login_pin', 'password', 'PIN / Password', false, '<span data-loading-text="<i class=\'fa fa-circle-o-notch fa-spin\'></i>" class="btn btn-edit amnp-lookup input-group-addon" tabindex="0" role="button" aria-label="Find details">Find details</span>', 'Enter your Library card number and PIN and we\'ll look up your details to fill in the next fields.').appendTo(self.loginForm); 138 } 139 } 140 } 141 } 142 143 self.detailsForm = $('<div></div>').appendTo(self.reserveScreenShared); 144 if (self.settings.mode !== 1 && self.settings.loginRequired) { 145 self.reserveScreen.addClass('amnp-login-required'); 146 self.detailsForm.hide(); 147 } 148 149 self._createField('contact_first_name', 'text', 'First name', true).appendTo(self.detailsForm); 150 self._createField('contact_last_name', 'text', 'Last name', true).appendTo(self.detailsForm); 151 self._createField('contact_email', 'text', 'Email', self.settings.emailRequired, false, 'We\'ll use the email address to confirm the booking and to notify if the room becomes unavailable.').appendTo(self.detailsForm); 152 self._createField('contact_tel', 'text', 'Phone', self.settings.phoneRequired, false).appendTo(self.detailsForm); 153 154 $('<input type="hidden" id="librarycard" value="">').appendTo(self.detailsForm); 155 156 if (self.settings.showSMSPrompt) { 157 var checked = self.settings.SMSOptMode ? 'checked="checked"' : ''; 158 var cbx = $('<div class="row amf-row">' + 159 ' <label for="phone" class="col-sm-4 control-label"> </label>' + 160 ' <div class="col-sm-8 field">' + 161 ' <div class="checkbox"><label style="font-size:12px;text-align:left;"><input style="width:auto !important;" type="checkbox" id="contact_allow_sms"' + checked + '/>' + self.settings.SMSPromptMessage + '</label></div>' + 162 ' </div></div>'); 163 cbx.appendTo(self.detailsForm); 164 } 165 166 this._setBooking(0); 167 $('<div class="amnp-booking-final-summary-heading link">Reservation details</div>').appendTo(self.confirmScreen); 168 self.bookingSummary = $('<div class="amnp-booking-summary"></div>').appendTo(self.confirmScreen); 169 $('<div class="amnp-booking-final-summary-heading link">Booking details</div>').appendTo(self.confirmScreen); 170 self.bookingDetails = $('<div class="amnp-booking-options"></div>').appendTo(self.confirmScreen); 171 172 self.bookingPayment = $('<div></div>').appendTo(self.confirmScreen).hide(); 173 if (self.settings.chargingEnabled && self.settings.ecommerceEnabled) $('<
173div class="amnp-booking-final-summary-heading link">Payment details</div>').appendTo(self.bookingPayment); 174 175 var buttons = $('<div class="amnp-buttons">' + '<span class="amnp-required-label"><span class="required-field"></span> field required</span>' + '<button class="btn btn-edit amnp-back-to-reserve">Cancel</button>' + '<button class="btn btn-success amnp-submit-booking">Reserve</button>' + '</div>').appendTo(self.confirmScreen); 176 if(self.settings.payLaterEnabled && this.settings.mode == 1) { 177 // Always build the staff payment type field; its visibility follows chargeability, 178 // which can change when the booking type is switched inside the booker. 179 self.staffPaymentTypeField = self._createField('payment_type', 'select', 'Payment type', false).appendTo(self.reserveScreenShared); 180 var $paymentTypeSelect = $('select', self.staffPaymentTypeField); 181 $paymentTypeSelect.append('<option value="offline">Offline</option>'); 182 $paymentTypeSelect.append('<option value="pay_later">Pay Later</option>'); 183 self.staffPaymentTypeField.toggle(this._isChargable()); 184 } 185 186 var paymentTypes = [ 187 { 188 string: 'card', 189 cond: self.settings.ecommerceEnabled && self.settings.cardPaymentEnabled, 190 buttonText: 'Card' 191 }, 192 { 193 string: 'pay_later', 194 cond: self.settings.ecommerceEnabled && self.settings.payLaterEnabled, 195 buttonText: self.settings.payLaterButtonText || 'Pay Later' 196 }, 197 { // CH#4337 - Offline text button customization 198 string: 'offline', 199 cond: self.settings.offlinePaymentsEnabled, 200 buttonText: self.settings.offlineButtonText || 'Offline' 201 } 202 ]; 203 204 // Determine which payment types are enabled and only show those buttons 205 var buttonHtml = ''; 206 paymentTypes.forEach(function (paymentType) { 207 if (paymentType['cond']) { 208 self.availablePaymentTypes.push(paymentType['string']); 209 var isActiveString = ''; 210 var isCheckedString = ''; 211 // Pre-select button for default payment type 212 if (self.bookingPaymentType === paymentType['string']) { 213 isActiveString = 'active'; 214 isCheckedString = 'checked'; 215 } 216 buttonHtml += 217 '<label class="btn btn-default ' + isActiveString + '">' 218 + '<input type="radio" name="options" value="' + paymentType['string'] + '" autocomplete="off" ' + isCheckedString + '>' 219 + paymentType['buttonText'] + 220 '</label>'; 221 } 222 }) 223 224 self.paymentTypeForm = $('<div class="row amf-row amnp-payment-toggle">' 225 + '<label class="col-sm-4 control-label">Payment type' 226 + '</span>' 227 + '</label>' 228 + '<div class="col-sm-8 field">' 229 + '<div class="btn-group" data-toggle="buttons" style="display:flex"}}>' 230 + buttonHtml 231 + '</div>' 232 + '</div>' 233 + '<div class="col-sm-4">' 234 + '</div>' 235 + '<div class="col-sm-8 am-form-desc">' 236 + '</div>' 237 + '</div>').appendTo(self.bookingPayment).hide(); 238 239 self.offlineForm = $('<div class="amnp-offline-form"></div>').html(self.settings.offlinePaymentsMessage).appendTo(self.bookingPayment).hide(); 240 var payLaterMessage = self.settings.laterPaymentsMessage + " " + self.settings.autoCancelNotice; 241 self.laterForm = $('<div class="amnp-paylater-form"></div>').html(payLaterMessage).appendTo(self.bookingPayment).hide(); 242 243 244 if (self.settings.ecommerceEnabled) { 245 self.PayPalPayFlow = new PayPalPayFlow('S', self.settings.testMode, self.settings.client); 246 self.PayPalPayFlow.buildForm().then(function (form) { 247 self.cardForm = form; 248 self.cardForm.appendTo(self.bookingPayment).hide(); 249 self._finishInterface(); 250 }); 251 } else { 252 self._finishInterface(); 253 } 254 }, 255 _finishInterface: function () { 256 var self = this; 257 $('<div class="amnp-spacer"><div class="amnp-error-message"></div></div>').appendTo(self.holder); 258 259 var $stage = $('.stage'); 260 if($stage.length > 0) { 261 $stage.scrollTop(0); 262 } 263 264 self._bindEvents(); 265 266 if (self.settings.mode === 1) { 267 self.cardlookup.show(); 268 } else { 269 if (self.settings.loginRequired) { 270 self.loginForm.show(); 271 self.detailsForm.hide(); 272 } 273 } 274 }, 275 _back: function () { 276 if (!this.currentBooking) { 277 this.$element.trigger('booker:closed', false); 278 } 279 var pos = this.bookings.indexOf(this.currentBooking); 280 if (pos > 0) { 281 this._setBooking(pos - 1); 282 } else { 283 this.$element.trigger('booker:closed', false); 284 } 285 }, 286 _next: function () { 287 var pos = this.bookings.indexOf(this.currentBooking) + 1; 288 if (pos < this.bookings.length) { 289 this._setBooking(pos); 290 } else { 291 this._showSummaryScreen(); 292 } 293 }, 294 _setBooking: function (pos) { 295 this.currentBooking = this.bookings[pos]; 296 this.reserveScreen.find('.amnp-booker-fields').remove(); 297 this._getBookingFields().appendTo(this.reserveScreen); 298 if (this.bookings.length > 1 || this.settings.chargingEnabled) { 299 if (pos > 0) { 300 this.reserveScreenShared.hide(); 301 } else { 302 this.reserveScreenShared.show(); 303 } 304 $('.amnp-booking-summary', this.reserveScreen).remove(); 305 $('.amnp-booking-final-summary-heading', this.reserveScreen).remove(); 306 this._getBookingSummeryDetails(this.currentBooking).prependTo(this.reserveScreen); 307 $('<div class="amnp-booking-final-summary-heading link">Reservation details (' + (pos + 1) + ' of ' + this.bookings.length + ')</div>').prependTo(this.reserveScreen); 308 } 309 }, 310 _showSummaryScreen: function () { 311 this.reserveScreen.hide(); 312 313 var self = this; 314 self.bookingSummary.empty();
315 this.bookings.forEach(function (booking) { 316 self._getBookingSummeryDetails(booking, self.settings.chargingEnabled).appendTo(self.bookingSummary); 317 }); 318 self.bookingDetails.empty(); 319 self._createField('contact_first_name', 'text', 'First name', false).appendTo(self.bookingDetails).find('input').val(self.currentBooking.data.contact.first_name).prop('readonly', true); 320 self._createField('contact_last_name', 'text', 'Last name', false).appendTo(self.bookingDetails).find('input').val(self.currentBooking.data.contact.last_name).prop('readonly', true); 321 self._createField('contact_tel', 'text', 'Phone', false, false).appendTo(self.bookingDetails).find('input').val(self.currentBooking.data.contact.phone).prop('readonly', true); 322 self._createField('contact_email', 'text', 'Email', false, false, 'We\'ll use the email address to confirm the booking and to notify if the room becomes unavailable.').appendTo(self.bookingDetails).find('input').val(self.currentBooking.data.contact.email).prop('readonly', true); 323 324 if (this._isChargable() && this.settings.chargingEnabled && this.settings.mode !== 1) { 325 var cost = this._calculateTotalCharge(); 326 327 if (this.availablePaymentTypes.length > 0) { 328 this.bookingPayment.show(); 329 if (this.availablePaymentTypes.length > 1) { 330 this.paymentTypeForm.show(); 331 } 332 if (this.availablePaymentTypes.includes(this.bookingPaymentType)) { 333 if (this.bookingPaymentType === 'card') { 334 this.cardForm.show(); 335 } else if (this.bookingPaymentType === 'offline') { 336 this.offlineForm.show(); 337 } else if (this.bookingPaymentType === 'pay_later') { 338 this.laterForm.show(); 339 } 340 } 341 } 342 } else { 343 if (this.paymentTypeForm) this.paymentTypeForm.hide(); 344 if (this.cardForm) this.cardForm.hide(); 345 if (this.offlineForm) this.offlineForm.hide(); 346 if (this.laterForm) this.laterForm.hide(); 347 if (this.bookingPayment) this.bookingPayment.hide(); 348 } 349 350 this.confirmScreen.show(); 351 }, 352 _getBookings: function () { 353 var self = this; 354 return self.bookings.reduce(function (ret, b) { 355 var booking = JSON.parse(JSON.stringify(b.data)); 356 var chargeamt = self._calculateBookingCharge(b, true); 357 358 booking.setup_time = 0; 359 booking.breakdown_time = 0; 360 361 booking.contact.group_name = booking.contact.group_name || booking.contact.first_name + ' ' + booking.contact.last_name.slice(0, 1); 362 booking.contact.booking_title = booking.contact.booking_title || booking.contact.first_name + ' ' + booking.contact.last_name.slice(0, 1); 363 booking.paymentType = self.bookingPaymentType; 364 365 if(self.settings.useSetupBreakdown) { 366 if(b.layout) { 367 //use layout setup/breakdown times 368 booking.setup_time = b.layout.setup_time; 369 booking.breakdown_time = b.layout.breakdown_time; 370 } else { 371 //use room setup/breakdown times 372 booking.setup_time = b.room.setup_time; 373 booking.breakdown_time = b.room.breakdown_time; 374 } 375 } 376 377 if (self._isChargable() && self.settings.mode !== 1 && self.settings.ecommerceEnabled && self.settings.chargingEnabled && chargeamt > 0 && booking.paymentType === 'card') { 378 379 if (!self._isFormClean(self.cardForm)) { 380 throw "Please enter information for all required fields"; 381 } 382 383 booking.paymentDetails = { 384 first_name: $('input#card_first_name', self.cardForm).val(), 385 last_name: $('input#card_last_name', self.cardForm).val(), 386 card_number: $('input#card_number', self.cardForm).val(), 387 expiration: $('input#expiration', self.cardForm).payment('cardExpiryVal'), 388 cvc: $('input#CVV2', self.cardForm).val(), 389 amt: chargeamt // Have to get the charge from the 'b' object not booking 390 }; 391 392 self.paypalAmount += parseFloat(booking.paymentDetails.amt); 393 394 if (booking.paymentType == 'card' && !$.payment.validateCardNumber(booking.paymentDetails.card_number)) { 395 throw "Invalid card number"; 396 } 397 if (booking.paymentType == 'card' && !$.payment.validateCardExpiry(booking.paymentDetails.expiration.month, booking.paymentDetails.expiration.year)) { 398 throw "Invalid expiration date"; 399 } 400 if (booking.paymentType == 'card' && !$.payment.validateCardCVC(booking.paymentDetails.cvc)) { 401 throw "Invalid cvc"; 402 } 403 if (booking.paymentType == 'card' && !$.payment.validateCardExpiry(booking.paymentDetails.expiration.month, booking.paymentDetails.expiration.year)) { 404 throw "Invalid expiration date"; 405 } 406 if (booking.paymentType == 'card' && !$.payment.validateCardCVC(booking.paymentDetails.cvc)) { 407 throw "Invalid cvc"; 408 } 409 } 410 411 booking.customQuestions = JSON.stringify(booking.customQuestions);
412 ret.push(booking); 413 return ret; 414 }, []); 415 }, 416 _resetEvents: function () { 417 this.paymentTypeForm.off('change', ':input'); 418 this.holder.off('click', '.amnp-submit-booking'); 419 this.holder.off('click', '.amnp-back-to-picker'); 420 this.holder.off('click', '.amnp-back-to-reserve'); 421 this.holder.off('click', '.amnp-lookup-history'); 422 this.holder.off('click', '.amnp-to-next-screen'); 423 this.holder.off('change', '.agreed'); 424 425 if (this.settings.mode === 1) { 426 this.reserveScreen.off('change', '#booking_type'); 427 this.reserveScreen.off('change', '#payment_type'); 428 this.reserveScreen.off('click', '.events2-lookup'); 429 this.reserveScreen.off('keydown', '.events2-lookup'); 430 } else { 431 this.loginForm.off('click', '.amnp-lookup'); 432 this.loginForm.off('keydown', '.amnp-lookup'); 433 } 434 }, 435 _bindEvents: function () { 436 var self = this; 437 self._resetEvents(); 438 439 self.paymentTypeForm.on('change', ':input', function () { 440 self.bookingPaymentType = $(this).val(); 441 var bookingForms = {'card': self.cardForm, 'offline': self.offlineForm, 'pay_later': self.laterForm}; 442 for (var paymentType in bookingForms) { 443 if (paymentType === self.bookingPaymentType) { 444 bookingForms[paymentType]?.show(); 445 } else { 446 bookingForms[paymentType]?.hide(); 447 } 448 } 449 }); 450 451 if (this.settings.mode == 1) { 452 this.reserveScreen.on('change', '#payment_type', function () { 453 self.bookingPaymentType = $(this).val(); 454 if (self.bookingPaymentType === 'pay_later') { 455 if (self.settings.autoCancelNotice && $('#auto-cancel-notice').length == 0) { 456 $('.am-input#payment_type').parent().append('<div id="auto-cancel-notice" class="am-form-desc"><div class="col-sm-4"></div><div class="col-sm-8">' + self.settings.autoCancelNotice + '</div></div>'); 457 } 458 } else { 459 $('#auto-cancel-notice').remove(); 460 } 461 }); 462 } 463 464 self.holder.on('click', '.amnp-submit-booking', function (event) { 465 event.preventDefault(); 466 var b = $(this).button('loading'); 467 var cache = []; 468 469 var runCharges = self.settings.chargingEnabled && self.settings.ecommerceEnabled && self.settings.cardPaymentEnabled && self.bookingPaymentType === 'card'; 470 471 try { 472 var finalBookings = self._getBookings(); 473 } catch (exception) { 474 $('.amnp-error-message', self.holder).text(exception).fadeIn().delay(6000).fadeOut(); 475 b.button('reset'); 476 return; 477 } 478 479 if (self.settings.mode === 1) { 480 // Control Panel 481 // No Payments 482 var toDo = finalBookings.length; 483 $.each(finalBookings, function (index, booking) { 484 var bookingData = self.bookings[index]; 485 var data = { 486 location_id: bookingData.location.id, 487 status: 0, 488 class_id: bookingData.class_id, 489 booking_type: bookingData.type, 490 contact_first_name: booking.contact.first_name, 491 contact_last_name: booking.contact.last_name, 492 contact_email: booking.contact.email, 493 contact_tel: booking.contact.phone, 494 contact_allow_sms: booking.contact.allow_sms ? 1 : 0, 495 group_name: booking.contact.group_name, 496 expected_attendees: booking.expected_attendees, 497 patron_notes: booking.patron_notes, 498 staff_notes: booking.staff_notes, 499 display_name: booking.contact.booking_title, 500 librarycard: booking.contact.librarycard, 501 start_time: booking.start_time, 502 end_time: booking.end_time, 503 setup_time: booking.setup_time, 504 breakdown_time: booking.breakdown_time, 505 assets: booking.assets, 506 room_id: booking.room_id, 507 custom_questions: booking.customQuestions, 508 layout_id: booking.layout_id, 509 paymentType: booking.paymentType, 510 booking_class: bookingData.class_id 511 }; 512 if (booking.paymentDetails) { 513 data.paymentDetails = booking.paymentDetails; 514 } 515 $.ajax({ 516 type: 'POST', 517 data: data, 518 dataType: 'json', 519 url: '/ajax/fetch/save_room_booking', 520 success: function (data) { 521 toDo--; 522 if (toDo <= 0) { 523 confirmChanges(); 524 showAlert(data); 525 b.button('reset'); 526 if (data.type === 'success') { 527 self.$element.trigger('booker:reset', true); 528 self.$element.trigger('booker:closed', true); 529 window.location.hash = '#rooms'; 530 } 531 } 532 } 533 }); 534 }); 535 } else { 536 // Website 537 // PayPal PayFlow 538 var blocks = ''; 539 var body = ''; 540 var head = ''; 541 var stage = $('<div class="events2-reg-thanks-stage"></div>'); 542 543 var toDo = finalBookings.length; 544 var cache = []; 545 var bookings = []; 546 var bookingTransactions = []; 547 548 self.paymentTypeForm.hide(); 549 self.reserveScreen.hide(); 550 self.confirmScreen.hide(); 551 self.screenThankyou.show().empty(); 552 553 // Setup a new stage, as we don't want the message. 554 if (runCharges) { 555 self.PayPalPayFlow.showProcessingAnimation($('.amnp-booker-holder')); 556 } else { 557 // We only want to show these once 558 stage.append('<div class="events2-reg-thanks-title">Your room booking information</div>'); 559 } 560 561 var foot = $('<div class="events2-reg-thanks-online">' + 562 ' <div class="events2-reg-thanks-online-title">View online</div>' + 563 ' <div>You can manage your room bookings at:</div>' + 564 ' <div><a href="/myreservations">' + window.location.hostname + '/myreservations</a></div>' + 565 '</div>'); 566 567 var sendBooking = function(booking, index) { 568 return $.ajax({ 569 url: '/reserve?action=submit', 570 data: booking, 571 dataType: 'json', 572 type: 'POST', 573 }).then(function (data) { 574 if (runCharges && booking.paymentDetails && booking.paymentDetails.amt > 0) { 575 data.amt = booking.paymentDetails.amt; 576 data.paymentType = self.bookingPaymentType; // booking.paymentType; 577 bookingTransactions.push({ json: data, booking: self.bookings[index] }); 578 } 579 580 // Cache should include all bookings, not just charged ones. 581 // merge the data with bookings data - regardless if new or not, we don't really care either way 582 for (var key in data) { 583 self.bookings[index].data[key] = data[key]; 584 } 585 var cacheData = self.bookings[index].data; 586 cacheData.loc = JSON.parse(JSON.stringify(self.bookings[index].location.name)); 587 cache.push(cacheData); 588 589 // Bookings needs to include all as well. 590 bookings.push({ json: data, booking: self.bookings[index] }); 591 return cache; 592 }); 593 }; 594 595 // Send bookings sequentially 596 finalBookings.reduce(function(acc, nextBooking, index) { 597 return acc.then(function() { 598 return sendBooking(nextBooking, index); 599 }); 600 }, Promise.resolve(cache)).then(function () { 601 stage.empty(); 602 603 for (var b = 0; b < bookings.length; b++) { 604 var book = bookings[b]; 605 // Get a formatted block per registration, this allows us to make a nicely designed 'stage' 606 blocks = self._getBookingMessageBlock(book.booking, book.json); 607 body = $('<div class="events2-reg-thanks-body"></div>'); 608 head = $('<div class="events2-reg-thanks-head"></div>'); 609 body.append(blocks.body); 610 head.append(blocks.head); 611 stage.append(head).append(body); 612 } 613 614 if (runCharges && bookingTransactions && bookingTransactions.length > 0) { 615 if (parseInt(self.settings.chargingMode, 10) === 1) {
616 $.post('/reserve', { action: 'set_booking_cache', bookingCache: cache }, function (cacheResponse) { 617 if (typeof cacheResponse.id !== 'undefined') { 618 new Promise(function (resolve, reject) { 619 if (typeof self.PayPalPayFlow !== 'undefined') { 620 if (bookingTransactions.length > 0) { 621 var paymentDetails = { 622 first_name: $('input#card_first_name', self.cardForm).val(), 623 last_name: $('input#card_last_name', self.cardForm).val(), 624 card_number: $('input#card_number', self.cardForm).val(), 625 expiration: $('input#expiration', self.cardForm).payment('cardExpiryVal'), 626 cvc: $('input#CVV2', self.cardForm).val(), 627 email: $('input#contact_email').val() 628 }; 629 630 paymentDetails.expiration = ("0" + paymentDetails.expiration.month.toString() + paymentDetails.expiration.year.toString().substr(2)).slice(-4); 631 paymentDetails.card_number = paymentDetails.card_number.replace(/\s+/g, ''); 632 633 var transactionData = { 634 'AMT': 0.00, 635 'CVV2': paymentDetails.cvc, 636 'EXPDATE': paymentDetails.expiration, 637 'ACCT': paymentDetails.card_number, 638 'BILLTOFIRSTNAME': paymentDetails.first_name, 639 'BILLTOLASTNAME': paymentDetails.last_name, 640 'EMAIL': paymentDetails.email, 641 'CURRENCY': self.settings.payment_currency ? self.settings.payment_currency : 'USD', 642 'CLIENT': self.settings.client 643 }; 644 645 self.PayPalPayFlow.sendReserveTransaction(transactionData, bookingTransactions, cacheResponse.id).then(function (response) { 646 // If we are back here, then we haven't browsed away, something is wrong with the bookings. 647 if (!response) { 648 reject(false); 649 } else { 650 resolve(); 651 } 652 }); 653 } else { 654 reject('Bookings array is empty.'); 655 } 656 } else { 657 reject('Form is broken.'); 658 } 659 }) 660 .then(function () { 661 // do nothing on success 662 return; 663 }) 664 .catch(function (response) { 665 console.error(response); 666 // If we are are here, then just draw the page as normal with whatever errors appear 667 if (self.PayPalPayFlow) self.PayPalPayFlow.hideProcessingAnimation(); 668 669 self.screenThankyou.empty(); 670 self.$element.trigger('booker:reset', true); 671 self.screenThankyou.append(stage).append(foot); 672 }); 673 } 674 }, 'json'); 675 } else { 676 // We've finished all the main drawing, time to append THANKS! 677 if (self.PayPalPayFlow) self.PayPalPayFlow.hideProcessingAnimation(); 678 679 self.screenThankyou.empty(); 680 self.$element.trigger('booker:reset', true); 681 self.screenThankyou.append(stage).append(foot); 682 } 683 } else { 684 // We've finished all the main drawing, time to append THANKS! 685 if (self.PayPalPayFlow) self.PayPalPayFlow.hideProcessingAnimation(); 686 687 self.screenThankyou.empty(); 688 self.$element.trigger('booker:reset', true); 689 self.screenThankyou.append(stage).append(foot); 690 } 691 }).catch(function(reason){ 692 console.error(reason); 693 }); 694 } 695 }); 696 697 self.holder.on('click', '.amnp-back-to-picker', function () { 698 self.$element.trigger('booker:closed', true); 699 }).on('change', '.agreed', function (event) { 700 event.preventDefault(); 701 if ($(this).is(':checked')) { 702 $('.amnp-to-next-screen', self.reserveScreen).removeProp('disabled'); 703 self.currentBooking.agreed = true; 704 } else { 705 self.currentBooking.agreed = false; 706 $('.amnp-to-next-screen', self.reserveScreen).prop('disabled', 'true'); 707 } 708 }).on('click', '.amnp-back-to-reserve', function (event) { 709 event.preventDefault(); 710 self._back(); 711 }).on('click', '.amnp-lookup-history', function (event) { 712 var search = $('#contact_first_name', self.holder).val(); 713 search += ' ' + $('#contact_last_name', self.holder).val(); 714 openDialog('/ajax/fetch/patron_finder?embedded=1&include_past=1&term=' + search + '&type=roombooking', 'Look up', function () { 715 addButton('.gldbuttons', '<i class="fa fa-times"></i>', 'btn-edit', function () { closeDialog(false); }); 716 }, function (status) { 717 }); 718 719 }).on('click', '.amnp-to-next-screen', function (event) { 720 event.preventDefault(); 721 722 var reserveWindow = document.getElementsByClassName('amnp-booker-holder'); 723 if(reserveWindow[0] !== undefined) reserveWindow[0].scrollIntoView({behavior: "smooth"}); 724 725 if (!self.currentBooking) self.currentBooking = self.bookings[0]; 726 if (self.settings.mode === 1) { 727 var bookingClass = $('#booking_type', self.holder).find(':selected').data('details'); 728 self.currentBooking.type = parseInt(bookingClass.type, 10) === 0 ? 1 : 2; 729 var paymentType = $('#payment_type', self.holder).val(); 730 self.currentBooking.data.paymentType = paymentType; 731 self.currentBooking.class_type = bookingClass.type; 732 self.currentBooking.class_id = bookingClass.id; 733 } 734 self.currentBooking.data.contact.first_name = $('#contact_first_name', self.holder).val(); 735 self.currentBooking.data.contact.last_name = $('#contact_last_name', self.holder).val(); 736 self.currentBooking.data.contact.phone = $('#contact_tel', self.holder).val(); 737 self.currentBooking.data.contact.allow_sms = $('#contact_allow_sms', self.holder).length > 0 ? $('#contact_allow_sms', self.holder).is(":checked") : 0; 738 self.currentBooking.data.contact.email = $('#contact_email', self.holder).val(); 739 self.currentBooking.data.contact.librarycard = $('#librarycard', self.holder).val(); 740 self.currentBooking.data.contact.group_name = $('#group_name', self.holder).val(); 741 self.currentBooking.data.contact.booking_title = $('#booking_title', self.holder).val(); 742 743 var patron = $(self.holder).data('patron'); 744 if (patron && typeof patron.mediationRequired !== 'undefined' && patron.mediationRequired !== null) { 745 self.currentBooking.data.mediationRequired = patron.mediationRequired ? 1 : 0; 746 } 747 748 $('input#card_first_name', self.cardForm).val(self.currentBooking.data.contact.first_name); 749 $('input#card_last_name', self.cardForm).val(self.currentBooking.data.contact.last_name); 750 751 self.currentBooking.data.expected_attendees = parseInt($('#expected_attendees', self.holder).val() || 0, 10); //TODO: we still need this 752 self.currentBooking.data.patron_notes = $('#patron_notes', self.holder).val(); 753 if (self.settings.mode === 1 && self.settings.enableStaffNotes) { 754 self.currentBooking.data.staff_notes = $('#staff_notes', self.holder).val() || ''; 755 } 756 self.currentBooking.data.customQuestions = {}; 757 var reemail = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 758 var rephone = new RegExp(self.settings.phone_number_regex, "i"); 759 var roomMax = Math.max(parseInt(self.currentBooking.room.capacity_standing, 10), parseInt(self.currentBooking.room.capacity_chairs, 10), parseInt(self.currentBooking.room.capacity_tables, 10)); 760 if (self.currentBooking.data.layout_id) { 761 $.each(self.currentBooking.room.layouts, function (idx, layout) { 762 if (layout.id === self.currentBooking.data.layout_id) { 763 roomMax = parseInt(layout.capacity, 10); 764 } 765 }); 766 } 767 if (self.settings.mode !== 1) { 768 if (!self._isFormClean(self.reserveScreen)) { 769 $('.amnp-error-message', self.holder).text('Please enter information for all required fields').fadeIn().delay(6000).fadeOut(); 770 return; 771 } 772 // CH#1957 - CK - Fix for when max is 0 to ignore the message. 773 if (self.settings.enforceMaxAttendees && (self.currentBooking.data.expected_attendees > roomMax) && roomMax > 0) { 774 $('.amnp-error-message', self.holder).text('Expected attendees exceeds capacity').fadeIn().delay(6000).fadeOut(); 775 $('#expected_attendees', self.holder).addClass("hightlight-field").delay(6000).queue(function () { 776 $(this).removeClass("hightlight-field").dequeue(); 777 }); 778 return; 779 } else if (self.settings.enableExpectedAttendees && self.settings.expectedAttendeesRequired && self.currentBooking.data.expected_attendees <= 0) { 780 $('.amnp-error-message', self.holder).text('Expected attendees cannot be less than one').fadeIn().delay(6000).fadeOut(); 781 $('#expected_attendees', self.holder).addClass("hightlight-field").delay(6000).queue(function () { 782 $(this).removeClass("hightlight-field").dequeue(); 783 }); 784 return; 785 } 786 if (!$('#contact_email', self.holder).prop('disabled') && self.currentBooking.data.contact.email.length > 0 && !reemail.test(self.currentBooking.data.contact.email)) { 787 $('.amnp-error-message', self.holder).text('Please enter a valid email address').fadeIn().delay(6000).fadeOut(); 788 $('#contact_email', self.holder).addClass("hightlight-field").delay(6000).queue(function () { 789 $(this).removeClass("hightlight-field").dequeue(); 790 }); 791 return; 792 } 793 if (!$('#contact_tel', self.holder).prop('disabled') && self.currentBooking.data.contact.phone.length > 0 && !rephone.test(self.currentBooking.data.contact.phone)) { 794 $('.amnp-error-message', self.holder).text('Please enter a valid phone number').fadeIn().delay(6000).fadeOut(); 795 $('#contact_tel', self.holder).addClass("hightlight-field").delay(6000).queue(function () { 796 $(this).removeClass("hightlight-field").dequeue(); 797 }); 798 799 return; 800 } 801 802 if (self.currentBooking.room.custom_fields && self.currentBooking.room.custom_fields.fields && self.currentBooking.room.custom_fields.fields.length > 0) { 803 for (var i = self.currentBooking.room.custom_fields.fields.length - 1; i >= 0; i--) { 804 var field = self.currentBooking.room.custom_fields.fields[i]; 805 var val = false;
806 switch (field.type) { 807 case 'checkbox': 808 val = $('#' + field.name, self.customQuestions).is(':checked'); 809 break; 810 default: 811 val = $('#' + field.name, self.customQuestions).val(); 812 break; 813 } 814 switch (field.validate) { 815 case 'custom': 816 var flags = field.regex.replace(/.*\/([gimy]*)$/, '$1'); 817 var pattern = field.regex.replace(new RegExp('^/(.*?)/' + flags + '$'), '$1'); 818 var regex = new RegExp(pattern, flags); 819 if (val.length > 0 && !regex.test(val)) { 820 $('.amnp-error-message', self.holder).text('The information entered is not valid').fadeIn().delay(6000).fadeOut(); 821 $('#' + field.name, self.holder).addClass("hightlight-field").delay(6000).queue(function () { 822 $(this).removeClass("hightlight-field").dequeue(); 823 }); 824 return; 825 } 826 break; 827 case 'email': 828 if (val.length > 0 && !reemail.test(val)) { 829 $('.amnp-error-message', self.holder).text('Please enter a valid email address').fadeIn().delay(6000).fadeOut(); 830 $('#' + field.name, self.holder).addClass("hightlight-field").delay(6000).queue(function () { 831 $(this).removeClass("hightlight-field").dequeue(); 832 }); 833 return; 834 } 835 break; 836 case 'phone': 837 if (val.length > 0 && !rephone.test(val)) { 838 $('.amnp-error-message', self.holder).text('Please enter a valid phone number').fadeIn().delay(6000).fadeOut(); 839 $('#' + field.name, self.holder).addClass("hightlight-field").delay(6000).queue(function () { 840 $(this).removeClass("hightlight-field").dequeue(); 841 }); 842 return; 843 } 844 break; 845 } 846 // An unticked checkbox is a real answer, so record it rather than dropping it, 847 // otherwise 'said no' and 'was never asked' look identical in the stored answers. 848 if (field.type === 'checkbox' || val) self.currentBooking.data.customQuestions[field.name] = val; 849 } 850 } 851 852 self._next(); 853 } else { 854 if (self.currentBooking.room.custom_fields && self.currentBooking.room.custom_fields.fields && self.currentBooking.room.custom_fields.fields.length > 0) { 855 for (var i = self.currentBooking.room.custom_fields.fields.length - 1; i >= 0; i--) { 856 var field = self.currentBooking.room.custom_fields.fields[i]; 857 var val = false; 858 switch (field.type) { 859 case 'checkbox': 860 val = $('#' + field.name, self.customQuestions).is(':checked'); 861 break; 862 default: 863 val = $('#' + field.name, self.customQuestions).val(); 864 break; 865 } 866 // An unticked checkbox is a real answer, so record it rather than dropping it, 867 // otherwise 'said no' and 'was never asked' look identical in the stored answers. 868 if (field.type === 'checkbox' || val) self.currentBooking.data.customQuestions[field.name] = val; 869 } 870 } 871 if (self.settings.enforceMaxAttendees && (self.currentBooking.data.expected_attendees > roomMax && roomMax > 0)) { 872 showAlert({ 873 title: 'Warning', 874 message: 'expected attendees exceeds capacity', 875 type: 'warning' 876 }); 877 } 878 879 if (parseInt(self.currentBooking.class_type, 10) > 0 && (self.currentBooking.data.contact.librarycard && self.currentBooking.data.contact.librarycard.length > 0 || self.currentBooking.data.contact.email && self.currentBooking.data.contact.email.length > 0)) { 880 $.getJSON('/ajax/fetch/check_room_booking', { room_id: self.currentBooking.data.room_id, start_time: self.currentBooking.data.start_time, end_time: self.currentBooking.data.end_time, librarycard: self.currentBooking.data.contact.librarycard, email: self.currentBooking.data.contact.email, class_id: self.currentBooking.class_id }, function (json) { 881 if (!json.ok) { 882 if (self.settings.staffRespectPatronLimit) { 883 showAlert({ 884 title: 'This will exceed the patrons booking limit', 885 message: json.message, 886 type: 'error' 887 }); 888 return; 889 } else { 890 if (json.far) { 891 var far = parseInt(self.currentBooking.room.restrictions.furthest_booking_days, 10); 892 showAlert({ 893 title: 'This will exceed the furthest booking day for the room (' + far + ' days)', 894 message: json.message, 895 type: 'warning' 896 }); 897 } else { 898 showAlert({ 899 title: 'This will exceed the patrons booking limit', 900 message: json.message, 901 type: 'warning' 902 }); 903 } 904 } 905 } 906 self._next(); 907 }); 908 } else { 909 self._next(); 910 } 911 } 912 }); 913 if (self.settings.mode === 1) { 914 self.reserveScreen.on('change', '#booking_type', function (event) { 915 event.preventDefault(); 916 var details = $(this).find(':selected').data('details'); 917 var locRooms = self.bookings.reduce(function (ret, booking) { 918 if (!ret[booking.room.location_id]) ret[booking.room.location_id] = []; 919 ret[booking.room.location_id].push(booking.room.id); 920 return ret; 921 }, {}); 922 for (var loc in locRooms) { 923 924 self._getJSON(self.settings.apiServer + '/v2/' + self.settings.client + '/rooms/' + loc, { room_id: locRooms[loc].join(','), class_id: $('#booking_type', self.reserveScreen).val() }).then(function (json) { 925 json.forEach(function (roomData) {
926 self.bookings.forEach(function (booking) { 927 if (booking.room.id == roomData.id) { 928 booking.room = roomData; 929 } 930 }); 931 932 // CH14585 - Fix for issues 933 self._updateCustomQuestions(null, null); 934 }); 935 if (self.staffPaymentTypeField) self.staffPaymentTypeField.toggle(self._isChargable()); 936 if (parseInt(details.type, 10) === 0) { 937 self.cardlookup.hide(); 938 self._getJSON('/ajax/fetch/logged_in_data').then(function (json) { 939 self.userData = json; 940 $('#contact_first_name', self.reserveScreen).val(self.userData.first_name); 941 $('#contact_last_name', self.reserveScreen).val(self.userData.last_name); 942 $('#contact_email', self.reserveScreen).val(self.userData.username); 943 }); 944 } else { 945 $('#contact_first_name', self.reserveScreen).val(''); 946 $('#contact_last_name', self.reserveScreen).val(''); 947 $('#contact_email', self.reserveScreen).val(''); 948 self.cardlookup.show(); 949 } 950 $('.amnp-booking-summary', self.reserveScreen).replaceWith(self._getBookingSummeryDetails(self.currentBooking)); 951 }).catch(function (err) { 952 console.log(err); 953 }); 954 } 955 }).on('click', '.events2-lookup', function (event) { 956 event.preventDefault(); 957 var b = $(this).button('loading'); 958 var card = $('#login_card', self.holder).val(); 959 self._getJSON('/ajax/fetch/patron_info', { 960 barcode: card, 961 type: 'reserve' 962 }).then(function (json, textStatus) { 963 $('#contact_first_name', self.holder).val(''); 964 $('#contact_last_name', self.holder).val(''); 965 $('#contact_tel', self.holder).val(''); 966 $('#contact_allow_sms', self.holder).val(''); 967 $('#contact_email', self.holder).val(''); 968 $('#librarycard', self.holder).val(''); 969 if (json.result === 'ok') { 970 if (json.data.names.length > 0) { 971 $('#contact_first_name', self.holder).val(json.data.names[0].first_name); 972 $('#contact_last_name', self.holder).val(json.data.names[0].last_name); 973 } 974 if (json.data.phones.length > 0) { 975 $('#contact_tel', self.holder).val(json.data.phones[0].replace(/\W/g, '')); 976 } 977 if (json.data.emails.length > 0) { 978 $('#contact_email', self.holder).val(json.data.emails[0]); 979 } 980 if (json.data.barcodes.length > 0) { 981 $('#librarycard', self.holder).val(json.data.barcodes[0]); 982 } 983 if (json.prefixBlocked) { 984 showAlert({ 985 title: 'Warning', 986 message: 'This card is not allowed to reserve rooms', 987 type: 'warning' 988 }); 989 } 990 } else { 991 showAlert({ 992 title: 'Failed', 993 message: 'Sorry, pin or card number not found', 994 type: 'warning' 995 }); 996 } 997 b.button('reset'); 998 }).catch(function (err) { 999 showAlert({ 1000 title: 'Error', 1001 message: 'Sorry, there was a problem making the request', 1002 type: 'error' 1003 }); 1004 b.button('reset'); 1005 }); 1006 }).on('keydown', '.events2-lookup', function (event) { 1007 var key = event.key; 1008 var keyCode = event.which || event.keyCode; 1009 if (key === 'Enter' || key === ' ' || key === 'Spacebar' || keyCode === 13 || keyCode === 32) { 1010 event.preventDefault(); 1011 $(this).trigger('click'); 1012 } 1013 }); 1014 } else { 1015 self.loginForm.on('click', '.amnp-lookup', function (event) { 1016 event.preventDefault(); 1017 var b = $(this).button('loading'); 1018 // CH#1942 - Trim the card/pin fields to prevent blanks. 1019 var card = $.trim($('#login_card', self.holder).val()); 1020 var loginData = { u: card, 'type': 'reserve' }; 1021 if (!self.settings.pinlessAuth) { 1022 loginData.p = $.trim($('#login_pin', self.holder).val()); 1023 } 1024 var locRooms = self.bookings.reduce(function (ret, booking) { 1025 ret.push(booking.room.id); 1026 return ret; 1027 }, []); 1028 if(locRooms.length > 0) { 1029 loginData.rooms = locRooms.join(','); 1030 } 1031 self._getJSON(self.settings.apiServer + '/v1/' + self.settings.client + '/patron', loginData).then(function (json, textStatus) { 1032 $(self.holder).removeData('patron'); 1033 $('#contact_first_name', self.holder).val(''); 1034 $('#contact_last_name', self.holder).val(''); 1035 $('#contact_tel', self.holder).val(''); 1036 $('#contact_allow_sms', self.holder).val(''); 1037 $('#contact_email', self.holder).val(''); 1038 $('#librarycard', self.holder).val(''); 1039 if (json.result == 'ok') { 1040 $(self.holder).data('patron', json.data); 1041 if (!self.settings.pinlessAuth) { 1042 if (json.data.names.length > 0) { 1043 $('#contact_first_name', self.holder).val(json.data.names[0].first_name); 1044 $('#contact_last_name', self.holder).val(json.data.names[0].last_name); 1045 if (self.settings.roomsPatronDetailsReadonly) { 1046 $('#contact_first_name', self.holder).prop('disabled', true); 1047 $('#contact_last_name', self.holder).prop('disabled', true); 1048 } 1049 } 1050 if (json.data.phones.length > 0) {
1051 $('#contact_tel', self.holder).val(json.data.phones[0].replace(/\W/g, '')); 1052 if (self.settings.roomsPatronDetailsReadonly) { 1053 $('#contact_tel', self.holder).prop('disabled', true); 1054 } 1055 } 1056 if (json.data.emails.length > 0) { 1057 $('#contact_email', self.holder).val(json.data.emails[0]); 1058 if (self.settings.roomsPatronDetailsReadonly) { 1059 $('#contact_email', self.holder).prop('disabled', true); 1060 } 1061 } 1062 } 1063 if (json.data.barcodes.length > 0) { 1064 $('#librarycard', self.holder).val(json.data.barcodes[0]); 1065 } 1066 if (self.settings.loginRequired) { 1067 self.reserveScreen.removeClass('amnp-login-required'); 1068 self.loginForm.hide(); 1069 self.detailsForm.show(); 1070 } 1071 } else { 1072 var msg = self.settings.pinlessAuth ? 'Sorry, card number not found' : 'Sorry, pin or card number not found'; 1073 if (json.message && json.message.length > 0) { 1074 msg = json.message; 1075 } 1076 $('.amnp-error-message', self.holder).text(json.message || msg).fadeIn().delay(6000).fadeOut(); 1077 } 1078 b.button('reset'); 1079 }).catch(function (err) { 1080 $('.amnp-error-message', self.holder).text('Sorry, there was a problem making the request').fadeIn().delay(6000).fadeOut(); 1081 b.button('reset'); 1082 }); 1083 }); 1084 } 1085 }, 1086 // Formatting block for CH#327 1087 _getBookingMessageBlock: function (booking, json) { 1088 // Booking - Room booking? 1089 // JSON - Custom object { amt(payment figure float), paymentType('card') } 1090 var self = this; 1091 1092 var message = '<i class="fa fa-check-circle"></i> Reservation For: '; 1093 var ref = (self.settings.mode !== 1) ? ' <div class="events2-reg-thanks-ref">Ref:<span class="events2-reg-thanks-ref-num">' + json.reference + '</span></div>' : ''; 1094 1095 var costMessage = ''; 1096 var mediatedMessage = ''; 1097 if (!json.ok) { 1098 message = '<span style="color:#ED0202;"><i class="fa fa-times-circle"></i> ' + json.message + '</span>'; 1099 ref = ''; 1100 } 1101 var lock = ''; 1102 1103 if (booking.room.unmediated === false || booking.data.mediationRequired === 1) { 1104 // Mediated 1105 mediatedMessage = '<div class="amrp-status-inreview"><i class="fa fa-info-circle"></i>' + ' ' + self.settings.rooms_mediated_booking_message + '</div>'; 1106 if (json.lock_code && json.lock_code.length > 0 && self.settings.rooms_remotelock_show_on_confirmationscreen_mediated) { 1107 lock = '<div class="events2-reg-thanks-pin"><span><i class="am-unlock"></i>Door code: </span>' + json.lock_code + '</div>' + 1108 '<div class="events2-reg-thanks-pin-message"><i class="fa fa-info-circle"></i>' + 1109 '<div class="events2-reg-thanks-pin-message-title">This room has a door lock.</div>' + 1110 '<div class="events2-reg-thanks-pin-message-body">You will need to enter the ' + json.lock_code.length + ' digit door code to unlock the room. Note: The code will not work before your room booking start time.</div>' + 1111 '</div>'; 1112 } 1113 } else { 1114 // Unmediated 1115 if (json.lock_code && json.lock_code.length > 0) { 1116 lock = '<div class="events2-reg-thanks-pin"><span><i class="am-unlock"></i>Door code: </span>' + json.lock_code + '</div>' + 1117 '<div class="events2-reg-thanks-pin-message"><i class="fa fa-info-circle"></i>' + 1118 '<div class="events2-reg-thanks-pin-message-title">This room has a door lock.</div>' + 1119 '<div class="events2-reg-thanks-pin-message-body">You will need to enter the ' + json.lock_code.length + ' digit door code to unlock the room. Note: The code will not work before your room booking start time.</div>' + 1120 '</div>'; 1121 } 1122 } 1123 1124 if (json.ok && this._isChargable()) { 1125 var cost = this._calculateBookingCharge(booking, true); 1126 if (cost > 0) { 1127 if (this.bookingPaymentType === 'card') { 1128 costMessage = '<div class="events2-cost-msg">The total cost of this booking was ' + this.settings.payment_currency_symbol + '' + this._formatCurrency(cost); 1129 costMessage += ((booking.room.unmediated === false || booking.data.mediationRequired === 1) ? ', payment will be collected once the booking is confirmed.' : ''); 1130 costMessage += '</div>'; 1131 } else if (this.bookingPaymentType === 'offline') { 1132 costMessage = '<div class="events2-cost-msg">The total cost of this booking was ' + this.settings.payment_currency_symbol + '' + this._formatCurrency(cost) + '.</div>'; 1133 } 1134 } 1135 } 1136 1137 var thanksHead = $('<div class="events2-reg-thanks-evtitle">' + booking.room.title + '</div>' + 1138 '<div class="events2-reg-thanks-evlocation"><i class="fa fa-map-marker"></i> ' + booking.location.name + '</div>'); 1139 1140 1141 var thanksBody = $('<div class="events2-reg-thanks-event">' + 1142 ' <div class="events2-reg-thanks-sub-title">' + message + '</div>' + 1143 (json.ok ? mediatedMessage : "") + 1144 ' <div class="events2-reg-thanks-evdate">' + booking.startTime.format('MMMM DD, YYYY') + '</div>' + 1145 ' <div class="events2-reg-thanks-evtime">' + booking.startTime.format('h:mma') + ' - ' + booking.endTime.format('h:mma') + '</div>' + 1146 ref + 1147 lock + 1148 costMessage + 1149 '</div>'); 1150 1151 if(self.settings.mode === 1) { 1152 var closeButton = $('<button style="margin-top: 30px;" class="btn btn-info">Close</button>').appendTo(thanksBody); 1153 1154 closeButton.on('click', function (event) { 1155 event.preventDefault(); 1156 event.stopPropagation(); 1157 1158 self.$element.trigger('booker:reset', true); 1159 self.$element.trigger('booker:closed', true); 1160 }); 1161 } 1162 1163 return { head: thanksHead, body: thanksBody }; 1164 }, 1165 _getBookingSummeryDetails: function (booking, forcePrice) { 1166 var $bookingSummary = $('<div class="amnp-booking-summary"></div>'); 1167 var $bookingSummaryDetails = $('<div class="amnp-booking-summary-details"></div>').appendTo($bookingSummary); 1168 var startTime = booking.startTime; 1169 var endTime = booking.endTime; 1170 1171 if (booking.room.image && booking.room.image.length > 0) { 1172 $('<img/>').attr('src', '//' + this.settings.client + '.libnet.info/images/roombooking/' + this.settings.client + '/' + booking.room.image).attr('alt', booking.room.title).appendTo($bookingSummaryDetails); 1173 } 1174 1175 var title = $('<div class="amnp-booking-summary-title">' + booking.room.title + '</div>').appendTo($bookingSummaryDetails); 1176 if (!this.settings.singleBranch) { 1177 title.append('<span>, ' + booking.location.name + '</span>'); 1178 } 1179 $('<div class="amnp-booking-summary-time">' + startTime.format('h:mma') + ' - ' + endTime.format('h:mma') + '</b>, ' + endTime.format('MMMM DD, YYYY') + '</div>').appendTo($bookingSummaryDetails); 1180 1181 var $bookingSummaryCost = $('<div class="amnp-booking-summary-total"></div>').appendTo($bookingSummary);
1182 var minutes = moment.duration(endTime.diff(startTime)).asMinutes(); 1183 var realmin = minutes % 60 1184 var hours = Math.floor(minutes / 60); 1185 var displayText = ''; 1186 if (hours > 0) { 1187 displayText = hours + (hours === 1 ? ' hour' : ' hours'); 1188 } 1189 if (realmin > 0) { 1190 displayText += ' ' + realmin + (realmin === 1 ? ' minute' : ' minutes'); 1191 } 1192 $('<div class="amnp-booking-summary-booking-name"></div>').text(displayText).appendTo($bookingSummaryCost); 1193 var costHolder = $('<div class="amnp-booking-summary-booking-cost"></div>').appendTo($bookingSummaryCost); 1194 if (this.settings.chargingEnabled || forcePrice) { 1195 costHolder.html(this.settings.payment_currency_symbol + '' + this._formatCurrency(this._calculateBookingCharge(booking, false))); 1196 } 1197 if (booking.data.assets.length > 0) { 1198 var $bookingSummaryAssets = $('<div class="amnp-booking-summary-assets"></div>').appendTo($bookingSummary); 1199 var self = this; 1200 booking.data.assets.forEach(function (asset) { 1201 var a = $('<div class="amnp-booking-summary-asset"></div>').appendTo($bookingSummaryAssets); 1202 $('<div class="amnp-booking-summary-booking-name"></div>').text(asset.name).appendTo(a); 1203 $('<div class="amnp-booking-summary-asset-qty"></div>').text('qty ' + asset.qty).appendTo(a); 1204 var costHolder = $('<div class="amnp-booking-summary-booking-cost"></div>').appendTo(a); 1205 if ((self.settings.mode !== 1 && self.settings.chargingEnabled) || forcePrice) { 1206 costHolder.html(self.settings.payment_currency_symbol + '' + self._formatCurrency(parseFloat(asset.cost) * asset.qty)); 1207 } 1208 $('<div class="amnp-booking-summary-booking-remove link">remove</div>').data('asset', asset).appendTo(a); 1209 }); 1210 } 1211 1212 var $bookingSummaryTotal = $('<div class="amnp-booking-summary-total"></div>').appendTo($bookingSummary); 1213 var $subTotal = $('<div class="amnp-booking-summary-booking-subtotal"></div>').appendTo($bookingSummaryTotal); 1214 var costHolder = $('<div class="amnp-booking-summary-booking-cost"></div>').appendTo($bookingSummaryTotal); 1215 $('<div class="amnp-booking-summary-booking-remove link">remove</div>').data('booking', booking).appendTo($bookingSummaryTotal); 1216 if (this.settings.chargingEnabled) { 1217 $subTotal.text('sub total'); 1218 costHolder.html(this.settings.payment_currency_symbol + '' + this._formatCurrency(this._calculateBookingCharge(booking, true))) 1219 } 1220 return $bookingSummary; 1221 }, 1222 _getBookingHeader: function (booking) { 1223 var $bookingHeader = $('<div></div>'); 1224 1225 var title = $('<div class="amnp-booker-summary"><div class="amnp-booker-title"><i class="am-locations"><b>' + booking.room.title + '</b></div>' + 1226 '<b>' + booking.startTime.format('h:mma') + ' - ' + booking.endTime.format('h:mma') + '</b>, ' + booking.endTime.format('MMMM DD, YYYY') + 1227 '<div class="amnp-booker-time"/></div>').appendTo($bookingHeader); 1228 1229 if (!this.settings.singleBranch) { 1230 $('.amnp-booker-title', title).append('<span>, ' + booking.location.name + '</span>'); 1231 } 1232 return $bookingHeader; 1233 }, 1234 _updateCustomQuestions: function ($form, last) { 1235 var customQuestions = null; 1236 var self = this; 1237 var pos = self.bookings.indexOf(self.currentBooking); 1238 var lastBooking; 1239 var $bookingForm = null; 1240 1241 if (pos > 0 && last === null) { 1242 lastBooking = self.bookings[pos - 1]; 1243 } 1244 1245 if ($form === null) { 1246 $bookingForm = $('<div class="amnp-booker-fields"></div>').data(self.currentBooking); 1247 } else { 1248 $bookingForm = $form; 1249 } 1250 1251 if ($('.amnp-custom-questions', $bookingForm).length > 0) { 1252 customQuestions = $('.amnp-custom-questions', $bookingForm); 1253 customQuestions.empty(); 1254 } else { 1255 customQuestions = $('<div class="amnp-custom-questions"></div>').appendTo($bookingForm); 1256 } 1257 1258 if (this.currentBooking.room.custom_fields.fields) { 1259 $.each(this.currentBooking.room.custom_fields.fields, function (index, val) { 1260 var field = self._createFieldWithObject(val).data('fieldSettings', val).addClass('amnp-custom-field').appendTo(customQuestions); 1261 if (val.type === 'select') { 1262 var select = $('select', field); 1263 $('<option value="">Please choose...</option>').appendTo(select); 1264 var options = val.options.trim().split(/\r?\n/); 1265 for (var i = 0; i < options.length; i++) { 1266 $('<option>' + options[i] + '</option>').appendTo(select); 1267 } 1268 } 1269 switch (val.type) { 1270 case 'checkbox': 1271 break; 1272 default: 1273 if (self.currentBooking.data.customQuestions && self.currentBooking.data.customQuestions[val.name]) { 1274 $('#' + val.name, $bookingForm).val(self.currentBooking.data.customQuestions[val.name]); 1275 } else if (lastBooking && lastBooking.data.customQuestions && lastBooking.data.customQuestions[val.name]) { 1276 $('#' + val.name, $bookingForm).val(lastBooking.data.customQuestions[val.name]); 1277 } 1278 break; 1279 } 1280 }); 1281 } 1282 $('.amnp-lookup', $bookingForm) 1283 .attr({ tabindex: 0, role: 'button' }) 1284 .off('keydown.lookup') 1285 .on('keydown.lookup', function (event) { 1286 var key = event.key; 1287 var keyCode = event.which || event.keyCode; 1288 if (key === 'Enter' || key === ' ' || key === 'Spacebar' || keyCode === 13 || keyCode === 32) { 1289 event.preventDefault(); 1290 $(this).trigger('click'); 1291 } 1292 }); 1293 } 1294 , 1295 _getBookingFields: function () { 1296 var pos = this.bookings.indexOf(this.currentBooking); 1297 var lastBooking; 1298 if (pos > 0) { 1299 lastBooking = this.bookings[pos - 1]; 1300 } 1301 1302 var $bookingForm = $('<div class="amnp-booker-fields"></div>').data(this.currentBooking); 1303 1304 if(parseInt(this.currentBooking.room.group_name_enabled, 10) === 1){ 1305 this._createField('group_name', 'text', 'Group name', true, false, '(This is <b>not displayed</b> to the public)').appendTo($bookingForm); 1306 var group_name = this.currentBooking.data.contact.group_name; 1307 if (group_name === "" && lastBooking) { 1308 group_name = lastBooking.data.contact.group_name 1309 } 1310 $('#group_name', $bookingForm).val(group_name); 1311 } 1312 1313 if(parseInt(this.currentBooking.room.booking_title_enabled, 10) === 1){ 1314 this._createField('booking_title', 'text', 'Booking title', true, false, '(This <b>will be displayed</b> to the public)').appendTo($bookingForm); 1315 var booking_title = this.currentBooking.data.contact.booking_title; 1316 if (booking_title === "" && lastBooking) { 1317 booking_title = lastBooking.data.contact.booking_title 1318 } 1319 $('#booking_title', $bookingForm).val(booking_title); 1320 } 1321 1322 if (this.settings.enableExpectedAttendees) { 1323 this._createField('expected_attendees', 'number', 'Attendees', this.settings.expectedAttendeesRequired, false, 'Number of attendees expected.', 0).appendTo($bookingForm); 1324 var expected_attendees = this.currentBooking.data.contact.expected_attendees; 1325 if (expected_attendees === "" && lastBooking) { 1326 expected_attendees = lastBooking.data.contact.expected_attendees 1327 } 1328 $('#expected_attendees', $bookingForm).val(expected_attendees); 1329 } 1330 1331 if (this.settings.enablePatronNotes && parseInt(this.currentBooking.room.patron_notes_enabled, 10) === 1) { 1332 this._createField('patron_notes', 'textarea', 'Notes', false, false, false).appendTo($bookingForm); 1333 $('#patron_notes', $bookingForm).val(this.currentBooking.data.patron_notes); 1334 } 1335 1336 if (this.settings.mode === 1 && this.settings.enableStaffNotes) {
1337 this._createField('staff_notes', 'textarea', 'Staff notes', false, false, false).appendTo($bookingForm); 1338 $('#staff_notes', $bookingForm).val(this.currentBooking.data.staff_notes); 1339 } 1340 1341 if (this.currentBooking.room.custom_fields && this.currentBooking.room.custom_fields.fields && this.currentBooking.room.custom_fields.fields.length > 0) { 1342 this._updateCustomQuestions($bookingForm, lastBooking); 1343 } 1344 var backlabel = this.bookings.indexOf(this.currentBooking) === 0 ? 'Cancel' : 'Back'; 1345 if (this.settings.mode === 1) { 1346 $('<div class="amnp-buttons"><button class="btn btn-edit amnp-lookup-history pull-left"><i class="am-search"/> Look up</button><button class="btn btn-edit amnp-back-to-reserve">' + backlabel + '</button><button class="btn btn-success amnp-to-next-screen">Next</button></div>').appendTo($bookingForm); 1347 } else { 1348 if (parseInt(this.currentBooking.room.tc_hidden, 10) === 0) { 1349 if (this.currentBooking.room.tc_link && this.currentBooking.room.tc_link.length > 0) { 1350 $('<label><input class="agreed" type="checkbox"> <span class="required-field"></span> I have read and agree to the room booking <a target="_blank" href="' + this.currentBooking.room.tc_link + '">terms and conditions</a></label>').appendTo($bookingForm); 1351 } else { 1352 $('<label><input class="agreed" type="checkbox"> <span class="required-field"></span> I have read and agree to the room booking terms and conditions</label>').appendTo($bookingForm); 1353 } 1354 } else { 1355 this.currentBooking.agreed = true; 1356 } 1357 $('<div class="amnp-buttons"><button class="btn btn-edit amnp-back-to-reserve">' + backlabel + '</button><button class="btn btn-success amnp-to-next-screen" disabled="true">Next</button></div>').appendTo($bookingForm); 1358 1359 if (this.currentBooking.agreed) { 1360 $('.agreed', $bookingForm).prop('checked', true); 1361 $('.amnp-to-next-screen', $bookingForm).removeProp('disabled'); 1362 } 1363 } 1364 1365 return $bookingForm; 1366 }, 1367 _isChargable: function () { 1368 return this._calculateTotalCharge() > 0; 1369 }, 1370 _formatCurrency: function (amount) { 1371 return amount.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); 1372 }, 1373 _calculateBookingCharge: function (booking, incAssets) { 1374 var roomTotal = parseFloat(booking.room.restrictions.charge_amount); 1375 1376 if (parseInt(booking.room.restrictions.charge_period) === 0) { 1377 var blockSize = parseInt(booking.room.restrictions.segment_size) * parseInt(booking.room.restrictions.block_count); 1378 var rawDuration = moment.duration(booking.endTime.diff(booking.startTime)).asMinutes(); 1379 roomTotal = (rawDuration / blockSize) * roomTotal; 1380 } 1381 1382 if (booking.layout && booking.layout.cost && parseFloat(booking.layout.cost) > 0) { 1383 roomTotal += parseFloat(booking.layout.cost); 1384 } 1385 if (incAssets) { 1386 var assetCharge = booking.data.assets.reduce(function (total, asset) { 1387 return total + parseFloat(asset.cost) * asset.qty; 1388 }, 0); 1389 } else { 1390 var assetCharge = 0; 1391 } 1392 return parseFloat(roomTotal + assetCharge); 1393 }, 1394 _calculateTotalCharge: function () { 1395 var self = this; 1396 var cartTotal = this.bookings.reduce(function (total, booking) { 1397 return total + self._calculateBookingCharge(booking, true); 1398 }, 0); 1399 return cartTotal; 1400 } 1401 }); 1402 $.fn[pluginName] = function (options) { 1403 return this.each(function () { 1404 if (!$.data(this, "plugin_" + pluginName)) { 1405 $.data(this, "plugin_" + pluginName, new Plugin(this, options)); 1406 } 1407 }); 1408 }; 1409})(jQuery, window, document);
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.