PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.11.3
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.11.3
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / assets / js / charitable.js

charitable.js in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.11.3, at assets/js/charitable.js

1,096 lines 31.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 CHARITABLE = window.CHARITABLE || {};
2
3 /**
4 * Set up Donation_Form object.
5 */
6 ( function( exports, $ ) {
7
8 /**
9 * Donation_Form expects a jQuery this.form object.
10 */
11 var Donation_Form = function( form ) {
12
13 /**
14 * Array of errors.
15 *
16 * @access public
17 */
18 this.errors = [];
19
20 /**
21 * Pending processes array.
22 *
23 * @access public
24 */
25 this.pending_processes = [];
26
27 /**
28 * Form object.
29 *
30 * @access public
31 */
32 this.form = form;
33
34 /**
35 * Flag to allow processing to be paused (i.e. while something like Stripe is processing).
36 *
37 * @access public
38 */
39 this.pause_processing = false;
40
41 /**
42 * Flag to prevent the on_submit handler from sending multiple concurrent AJAX requests
43 *
44 * @access public
45 */
46 this.submit_processing = false;
47
48 /**
49 * The donation amount.
50 *
51 * @access public
52 */
53 this.total = 0;
54
55 /**
56 * Whether to scroll to the top of the form after a submission with errors.
57 *
58 * @access private
59 */
60 this.prevent_scroll_to_top = false;
61
62 /**
63 * Reference to this object.
64 *
65 * @access private
66 */
67 var self = this;
68
69 /**
70 * Body element reference.
71 *
72 * @access private
73 */
74 var $body = $( 'body' );
75
76 /**
77 * Handle click of terms link.
78 *
79 * @access private
80 */
81 var on_click_terms = function() {
82 self.form.find( '.charitable-terms-text' ).addClass( 'active' );
83 return false;
84 };
85
86 /**
87 * Focus event handler on custom donation amount input field.
88 *
89 * @access private
90 */
91 var on_focus_custom_amount = function() {
92 $( this ).closest( 'li' ).trigger( 'click' ).find( 'input[name=donation_amount]' ).prop( 'checked', true ).trigger( 'change' );
93
94 self.form.off( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
95
96 $( this ).focus();
97
98 self.form.on( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
99 };
100
101 /**
102 * Trigger jQuery events to broadcast the change in donation amount.
103 *
104 * @since 1.6.19
105 *
106 * @access private
107 */
108 var trigger_amount_change_events = function() {
109 /* The chosen donation amount has changed. */
110 $body.trigger( 'charitable:form:amount:changed', self );
111
112 /* The overall donation amount has changed. */
113 $body.trigger( 'charitable:form:total:changed', self );
114 };
115
116 /**
117 * Focus event handler for changes to custom donation amount input field.
118 *
119 * @access private
120 */
121 var on_change_custom_donation_amount = function() {
122 var unformatted = self.unformat_amount( $( this ).val() );
123
124 if ( $.trim( unformatted ) > 0 ) {
125 $( this ).val( self.format_amount( unformatted ) );
126 }
127
128 trigger_amount_change_events();
129 };
130
131 /**
132 * Select a donation amount.
133 *
134 * @return void
135 */
136 var on_select_donation_amount = function() {
137 var $li = $( this ).closest( 'li' );
138
139 // Already selected, quit early to prevent focus/change loop
140 if ( $li.hasClass( 'selected' ) ) {
141 return;
142 }
143
144 $li.parents( '.charitable-donation-form' ).find( '.donation-amount.selected' ).removeClass( 'selected' );
145
146 $li.addClass( 'selected' ).find( '[name=donation_amount]' ).prop( 'checked', true );
147
148 if ( $li.hasClass( 'custom-donation-amount' ) ) {
149 $li.find( 'input.custom-donation-input' ).focus();
150 }
151
152 trigger_amount_change_events();
153 };
154
155 /**
156 * Change event handler for payment gateway selector.
157 *
158 * @access private
159 */
160 var on_change_payment_gateway = function() {
161 var selectedGateway = $(this).val();
162
163 self.hide_inactive_payment_methods();
164 self.show_active_payment_methods( selectedGateway );
165
166 // Trigger resize event for Square gateway to fix iframe height issue
167 if ( selectedGateway === 'square' || selectedGateway === 'square_core' ) {
168 setTimeout(function() {
169 window.dispatchEvent(new Event('resize'));
170 }, 100);
171 }
172 };
173
174 /**
175 * Event handler when the "Change" link is clicked for pre-filled donations.
176 */
177 var on_click_change_amount_link = function() {
178 $(this).parent().addClass( 'charitable-hidden' );
179 };
180
181 /**
182 * Submit event handler for donation form.
183 *
184 * @access private
185 */
186 var on_submit = function( event ) {
187 var helper = event.data.helper;
188
189 if ( helper.submit_processing ) {
190 return false;
191 }
192
193 helper.submit_processing = true;
194
195 /* Display the processing spinner and hide the button */
196 helper.show_processing();
197
198 /* Validate the form submission before going further. */
199 if ( false === helper.validate() ) {
200 helper.cancel_processing();
201 return false;
202 }
203
204 /* If processing has been paused, return false now. */
205 if ( false !== helper.pause_processing ) {
206 return false;
207 }
208
209 /* If we're not using AJAX to process the donation further, return now. */
210 if ( 1 !== helper.form.data( 'use-ajax' ) ) {
211 return true;
212 }
213
214 /* Continue on to process the donation. */
215 maybe_process( helper, function() {
216 $body.trigger( 'charitable:form:process', helper );
217 } );
218
219 return false;
220 };
221
222 /**
223 * Check whether there are any asynchronous threads we need to wait for.
224 *
225 * If not, trigger processing. If there are, check again in 500ms.
226 */
227 var maybe_process = function( helper, callback ) {
228 if ( ! helper.waiting() ) {
229 /* Double-check that there are still no errors. */
230 if ( helper.get_errors().length > 0 ) {
231 helper.cancel_processing();
232 } else {
233 callback();
234 }
235
236 return;
237 }
238
239 setTimeout( maybe_process, 500, helper, callback );
240 }
241
242 /**
243 * Process the donation.
244 *
245 * This is a callback for the 'charitable:form:process' event. It's called
246 * after validation has taken place.
247 *
248 * @param object Event
249 * @param object Donation_Form
250 */
251 var process_donation = function( event, helper ) {
252
253 var data = helper.get_data();
254
255 var form = helper.form;
256
257 /* Cancel the default Charitable action, but pass it along as the form_action variable */
258 data.action = 'make_donation';
259 data.form_action = data.charitable_action;
260 delete data.charitable_action;
261
262 $.ajax({
263 type: "POST",
264 data: data,
265 dataType: "json",
266 url: CHARITABLE_VARS.ajaxurl,
267 timeout: 0,
268 xhrFields: {
269 withCredentials: true
270 },
271 success: function (response) {
272 $body.trigger( 'charitable:form:processed', [ response, helper ] );
273
274 if ( response.success ) {
275 maybe_process( helper, function() {
276 window.location.href = response.redirect_to;
277 } );
278 }
279 else {
280 helper.cancel_processing( response.errors );
281
282 if ( response.donation_id ) {
283 helper.set_donation_id( response.donation_id );
284 }
285 }
286 }
287 }).fail(function (response, textStatus, errorThrown) {
288 if ( window.console && window.console.log ) {
289 console.log( response );
290 }
291
292 helper.cancel_processing( [ CHARITABLE_VARS.error_unknown ] );
293 });
294
295 return false;
296 }
297
298 /**
299 * Set up event handlers for donation forms.
300 *
301 * @return void
302 */
303 var init = function() {
304 // Init donation amount selection
305 self.form.on( 'click', '.donation-amount', on_select_donation_amount );
306 self.form.on( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
307 self.form.on( 'click', '.charitable-terms-link', on_click_terms );
308
309 // Init currency formatting
310 self.form.on( 'blur', '.custom-donation-input', on_change_custom_donation_amount );
311
312 self.form.find( '.donation-amount input:checked' ).each( function() {
313 $( this ).closest( 'li' ).addClass( 'selected' );
314 });
315
316 if ( self.get_all_payment_methods().length ) {
317 self.hide_inactive_payment_methods();
318 self.form.on( 'change', '#charitable-gateway-selector input[name=gateway]', on_change_payment_gateway );
319 }
320
321 self.form.on( 'click', '.change-donation', on_click_change_amount_link );
322
323 // Handle donation form submission
324 self.form.on( 'submit', {
325 helper : self,
326 }, on_submit );
327
328 // This only fires after the first form instance is initialized.
329 if ( false === CHARITABLE.forms_initialized ) {
330
331 // Process the donation on the 'charitable:form:process' event.
332 $body.on( 'charitable:form:process', process_donation );
333
334 $body.trigger( 'charitable:form:initialize', self );
335
336 CHARITABLE.forms_initialized = true;
337 }
338
339 $body.trigger( 'charitable:form:loaded', self );
340 }
341
342 init();
343 };
344
345 /**
346 * Return a particular input field.
347 *
348 * @param string The field name to retrieve.
349 * @return object
350 */
351 Donation_Form.prototype.get_input = function( field ) {
352 return this.form.find( '[name=' + field + ']' );
353 }
354
355 /**
356 * Return the submitted email address.
357 *
358 * @return string
359 */
360 Donation_Form.prototype.get_email = function() {
361 return this.form.find( '[name=email]' ).val();
362 };
363
364 /**
365 * Returns whether this is a recurring donation.
366 *
367 * @since 1.4.21
368 *
369 * @return boolean
370 */
371 Donation_Form.prototype.is_recurring_donation = function() {
372 var recurring = this.form.find( '[name=recurring_donation]:checked, [name=recurring_donation][type=hidden]' );
373
374 return recurring.length && 'once' !== recurring.val();
375 };
376
377 /**
378 * Get the chosen suggested amount.
379 *
380 * Note that when no option has been selected, or a custom donation amount has been added,
381 * this will return 0.
382 *
383 * @since 1.4.19
384 *
385 * @return float
386 */
387 Donation_Form.prototype.get_suggested_amount = function() {
388 return accounting.unformat(
389 this.form.find( '[name=donation_amount]:checked, input[type=hidden][name=donation_amount]' ).val(),
390 CHARITABLE_VARS.currency_format_decimal_sep
391 );
392 }
393
394 /**
395 * Get the custom amount.
396 *
397 * Note that this will return 0 when no custom amount has been entered or a 0 has been entered.
398 *
399 * @since 1.4.19
400 *
401 * @return float
402 */
403 Donation_Form.prototype.get_custom_amount = function() {
404 var input = this.form.find( '.charitable-donation-options.active input.custom-donation-input,.charitable-donation-options.active input.custom-donation-amount' );
405
406 if ( 0 === input.length ) {
407 input = this.form.find( 'input.custom-donation-input' );
408 }
409
410 return accounting.unformat(
411 input.val(),
412 CHARITABLE_VARS.currency_format_decimal_sep
413 );
414 }
415
416 /**
417 * Get the donation subtotal, which is the amount passed by the donor.
418 *
419 * Notably, this does not include any additional amounts that may be added onto the donation
420 * total, such as processing fees that the donor agrees to pay.
421 *
422 * @since 1.6.7
423 *
424 * @return float
425 */
426 Donation_Form.prototype.get_subtotal = function() {
427 return this.get_suggested_amount() || this.get_custom_amount();
428 };
429
430 /**
431 * Get the submitted amount, taking into account both the custom & suggested donation fields.
432 *
433 * @return float
434 */
435 Donation_Form.prototype.get_amount = function() {
436 this.total = this.get_subtotal();
437
438 this.form.trigger( 'charitable:form:get_amount', this );
439
440 return this.total;
441 };
442
443 /**
444 * Get the submitted amount, taking into account both the custom & suggested donation fields.
445 *
446 * @since 1.6.8
447 *
448 * @param float add
449 * @return void
450 */
451 Donation_Form.prototype.add_amount = function( add ) {
452 this.total += add;
453 };
454
455 /**
456 * Get the submitted amount, taking into account both the custom & suggested donation fields.
457 *
458 * @since 1.6.8
459 *
460 * @param float remove Amount to be removed
461 * @return void
462 */
463 Donation_Form.prototype.remove_amount = function( remove ) {
464 this.total -= remove;
465 };
466
467 /**
468 * Get a description of the donation.
469 *
470 * @return string
471 */
472 Donation_Form.prototype.get_description = function() {
473 return this.form.find( '[name=description]' ).val() || '';
474 };
475
476 /**
477 * Get credit card number.
478 *
479 * @return string
480 */
481 Donation_Form.prototype.get_cc_number = function() {
482 return this.form.find( '#charitable_field_cc_number input' ).val() || '';
483 };
484
485 /**
486 * Get credit card CVC number.
487 *
488 * @return string
489 */
490 Donation_Form.prototype.get_cc_cvc = function() {
491 return this.form.find( '#charitable_field_cc_cvc input' ).val() || '';
492 };
493
494 /**
495 * Get credit card expiry month.
496 *
497 * @return string
498 */
499 Donation_Form.prototype.get_cc_expiry_month = function() {
500 return this.form.find( '#charitable_field_cc_expiration select.month' ).val() || '';
501 };
502
503 /**
504 * Get credit card expiry year.
505 *
506 * @return string
507 */
508 Donation_Form.prototype.get_cc_expiry_year = function() {
509 return this.form.find( '#charitable_field_cc_expiration select.year' ).val() || '';
510 };
511
512 /**
513 * Clear credit card fields.
514
515 *
516 * This is used by gateways that create tokens through Javascript (such as Stripe), to
517 * avoid credit card details hitting the server.
518 *
519 * @return void
520 */
521 Donation_Form.prototype.clear_cc_fields = function() {
522 this.form.find( '#charitable_field_cc_number input, #charitable_field_cc_name input, #charitable_field_cc_cvc input, #charitable_field_cc_expiration select' ).removeAttr( 'name' );
523 };
524
525 /**
526 * Return the selected payment method.
527 *
528 * @return string
529 */
530 Donation_Form.prototype.get_payment_method = function() {
531 return this.form.find( '[type=hidden][name=gateway], [name=gateway]:checked' ).val() || '';
532 };
533
534 /**
535 * Return all payment methods.
536 *
537 * @return object
538 */
539 Donation_Form.prototype.get_all_payment_methods = function() {
540 return this.form.find( '#charitable-gateway-selector input[name=gateway]' );
541 }
542
543 /**
544 * Hide inactive payment methods.
545 *
546 * @return void
547 */
548 Donation_Form.prototype.hide_inactive_payment_methods = function() {
549 var active = this.get_payment_method();
550 var fields = this.form.find( '.charitable-gateway-fields[data-gateway!=' + active + ']' );
551
552 fields.hide();
553 fields.find( 'input[required],select[required],textarea[required]' ).attr( 'data-required', 1 ).attr( 'required', false );
554 };
555
556 /**
557 * Show active payment methods.
558 *
559 * @return void
560 */
561 Donation_Form.prototype.show_active_payment_methods = function( active ) {
562 var active = active || this.get_payment_method();
563 var fields = this.form.find( '.charitable-gateway-fields[data-gateway=' + active + ']' );
564
565 fields.show();
566 fields.find( '[data-required]' ).attr( 'required', true );
567 };
568
569 /**
570 * Select a donation amount.
571 *
572 * @param int price
573 * @param string symbol
574 * @return string
575 */
576 Donation_Form.prototype.format_amount = function( price, symbol ){
577 if ( typeof symbol === 'undefined' )
578 symbol = '';
579
580 return accounting.formatMoney( price, {
581 symbol : symbol,
582 decimal : CHARITABLE_VARS.currency_format_decimal_sep,
583 thousand: CHARITABLE_VARS.currency_format_thousand_sep,
584 precision : CHARITABLE_VARS.currency_format_num_decimals,
585 format: CHARITABLE_VARS.currency_format
586 }).trim();
587 };
588
589 /**
590 * Unformat a currency string to a plain float.
591 *
592 * Handles the case where the site uses a non-period decimal separator (e.g. comma)
593 * but the user types a period as the decimal point — a common mistake on non-US
594 * keyboards. accounting.unformat() would otherwise strip the period as if it were
595 * a thousands separator, turning "5.33" into 533.
596 *
597 * Heuristic: if the configured decimal is not a period, the value contains a period,
598 * and the value does NOT already contain the configured decimal separator:
599 * - One period followed by 1–2 digits at end → treat as decimal (e.g. "5.33" → "5,33")
600 * - One period followed by exactly 3 digits at end → treat as thousands (e.g. "1.000" → 1000)
601 * - Multiple periods → all thousands separators; let accounting.unformat strip them
602 *
603 * @param string|number price
604 * @return float
605 */
606 Donation_Form.prototype.unformat_amount = function( price ) {
607 var decimal = CHARITABLE_VARS.currency_format_decimal_sep;
608
609 price = String( price );
610
611 if ( decimal !== '.' && price.indexOf( '.' ) !== -1 && price.indexOf( decimal ) === -1 ) {
612 var periodCount = ( price.match( /\./g ) || [] ).length;
613
614 if ( periodCount === 1 && ! /\.\d{3}$/.test( price ) ) {
615 // Single period not followed by exactly 3 digits — treat as decimal separator.
616 price = price.replace( '.', decimal );
617 }
618 // Single period followed by 3 digits (thousands) or multiple periods:
619 // fall through and let accounting.unformat strip them.
620 }
621
622 return Math.abs( parseFloat( accounting.unformat( price, decimal ) ) );
623 };
624
625 /**
626 * Add an error message.
627 *
628 * @param string message
629 * @return void
630 */
631 Donation_Form.prototype.add_error = function( message ) {
632 this.errors.push( message );
633 };
634
635 /**
636 * Return the errors.
637 *
638 * @return []
639 */
640 Donation_Form.prototype.get_errors = function() {
641 return this.errors;
642 };
643
644 /**
645 * Prints out a nice string describing the errors.
646 *
647 * @return string
648 */
649 Donation_Form.prototype.get_error_message = function() {
650 return this.errors.join( ' ' );
651 };
652
653 /**
654 * Print the errors at the top of the form.
655 *
656 * @param array
657 */
658 Donation_Form.prototype.print_errors = function( errors ) {
659 var e = errors || this.errors,
660 i = 0,
661 count = e.length,
662 output = '',
663 error_notice_donation_display = typeof CHARITABLE_VARS.error_notice_donation_display === 'undefined' ? 'top' : CHARITABLE_VARS.error_notice_donation_display;
664
665 if ( 0 === count ) {
666 return;
667 }
668
669 if ( this.form.find( '.charitable-form-errors' ).length ) {
670 this.form.find( '.charitable-form-errors' ).remove();
671 }
672
673 output += '<div class="charitable-form-errors charitable-notice"><ul class="errors"><li>';
674 output += e.join( '</li><li>' );
675 output += '</li></ul></div>';
676
677 if ( error_notice_donation_display === 'bottom' ) {
678 this.form.append( output );
679 } else {
680 this.form.prepend( output );
681 }
682
683 }
684
685 /**
686 * Clear the errors and remove the printed errors.
687 */
688 Donation_Form.prototype.clear_errors = function() {
689 this.errors = [];
690
691 if ( this.form.find( '.charitable-form-errors' ).length ) {
692 this.form.find( '.charitable-form-errors' ).remove();
693 }
694 }
695
696 /**
697 * Return whether we are waiting for an asynchronous process to finish.
698 *
699 * @since 1.6.9
700 *
701 * @return boolean
702 */
703 Donation_Form.prototype.waiting = function() {
704 return this.pending_processes.length > 0;
705 }
706
707 /**
708 * Add a pending process.
709 *
710 * @since 1.6.9
711 *
712 * @param string Process identifier.
713 * @return int Index of the process.
714 */
715 Donation_Form.prototype.add_pending_process = function( process ) {
716 var index = this.pending_processes.indexOf( process );
717 return -1 === index ? ( this.pending_processes.push( process ) - 1 ) : index;
718 }
719
720 /**
721 * Remove a pending process by index.
722 *
723 * @since 1.6.9
724 *
725 * @param int Index of the process.
726 * @return void
727 */
728 Donation_Form.prototype.remove_pending_process = function( index ) {
729 this.pending_processes.splice( index, 1 );
730 }
731
732 /**
733 * Remove a pending process by process name.
734 *
735 * @since 1.6.17
736 *
737 * @param string Name of the process.
738 * @return void
739 */
740 Donation_Form.prototype.remove_pending_process_by_name = function( process ) {
741 var index = this.pending_processes.indexOf( process );
742 return -1 !== index && this.remove_pending_process( index );
743 }
744
745 /**
746 * Show that the donation form is processing.
747 */
748 Donation_Form.prototype.show_processing = function() {
749 this.form.find( '.charitable-form-processing' ).show();
750 this.form.find( 'button[name="donate"]' ).hide();
751 }
752
753 /**
754 * Hide the processing spinner and show the donate button.
755 */
756 Donation_Form.prototype.hide_processing = function() {
757 this.form.find( '.charitable-form-processing' ).hide();
758 this.form.find( 'button[name="donate"]' ).show();
759
760 this.submit_processing = false;
761 this.pause_processing = false;
762 }
763
764 /**
765 * Scroll to the top of the form.
766 */
767 Donation_Form.prototype.scroll_to_top = function() {
768 if ( this.prevent_scroll_to_top ) {
769 this.prevent_scroll_to_top = false;
770 return;
771 }
772
773 var $modal = this.form.parents( '.charitable-modal' );
774
775 if ( $modal.length ) {
776 $modal.scrollTop( 0 );
777 }
778 else {
779 window.scrollTo( this.form.offset().left, this.form.offset().top );
780 }
781 };
782
783 /**
784 * Cancel donation processing.
785 *
786 * @param array errors Error messages to show.
787 */
788 Donation_Form.prototype.cancel_processing = function( errors ) {
789 this.hide_processing();
790 this.print_errors( errors );
791 var error_notice_donation_display = typeof CHARITABLE_VARS.error_notice_donation_display === 'undefined' ? 'top' : CHARITABLE_VARS.error_notice_donation_display;
792 if ( '' === error_notice_donation_display || 'top' === error_notice_donation_display ) {
793 this.scroll_to_top();
794 }
795
796 this.form.trigger( 'charitable:form:cancelled', this );
797 };
798
799 /**
800 * Set the donation ID.
801 */
802 Donation_Form.prototype.set_donation_id = function( donation_id ) {
803 this.form.find( '[name=ID]' ).val( donation_id );
804 }
805
806 /**
807 * Returns all of the submitted data as key=>value object.
808 *
809 * @return object
810 */
811 Donation_Form.prototype.get_data = function() {
812 return this.form.serializeArray().reduce( function( obj, item ) {
813 if ( '[]' === item.name.slice( -2 ) ) {
814 var name = item.name.slice( 0, -2 );
815
816 if ( ! obj.hasOwnProperty( name ) ) {
817 obj[name] = [];
818 }
819
820 obj[name].push( item.value );
821 } else {
822 obj[ item.name ] = item.value;
823 }
824
825 return obj;
826 }, {} );
827
828 };
829
830 /**
831 * Returns all of the required fields.
832 *
833 * @return object
834 */
835 Donation_Form.prototype.get_required_fields = function() {
836 var fields = this.form.find( '.charitable-fieldset .required-field' ).not( '#charitable-gateway-fields .required-field' ),
837 method = this.get_payment_method();
838
839 if ( '' !== method ) {
840
841 var gateway_fields = this.form.find( '[data-gateway=' + method + '] .required-field' );
842
843 if ( gateway_fields.length ) {
844 fields = $.merge( fields, gateway_fields );
845 }
846
847 }
848
849 return fields;
850 };
851
852 /**
853 * Check whether the submitted amount is greater than the maximum permitted.
854 *
855 * @returns boolean
856 */
857 Donation_Form.prototype.subtotal_exceeds_maximum = function() {
858 return '' !== CHARITABLE_VARS.maximum_donation && this.get_subtotal() > parseFloat( CHARITABLE_VARS.maximum_donation );
859 }
860
861 /**
862 * Make sure that the submitted amount is valid.
863 *
864 * @return boolean
865 */
866 Donation_Form.prototype.is_valid_amount = function() {
867 var minimum = parseFloat( CHARITABLE_VARS.minimum_donation );
868
869 if ( this.subtotal_exceeds_maximum() ) {
870 return false;
871 }
872
873 return minimum > 0 || CHARITABLE_VARS.permit_0_donation
874 ? this.get_subtotal() >= minimum
875 : this.get_subtotal() > minimum;
876 };
877
878 /**
879 * Validate the amount. If not valid, set an error.
880 *
881 * @return boolean
882 */
883 Donation_Form.prototype.validate_amount = function() {
884 if ( false === this.is_valid_amount() ) {
885 if ( this.get_subtotal() > 0 && this.subtotal_exceeds_maximum() ) {
886 this.add_error( CHARITABLE_VARS.error_max_exceeded );
887 } else {
888 this.add_error( CHARITABLE_VARS.error_invalid_amount );
889 }
890 return false;
891 }
892
893 return true;
894 };
895
896 /**
897 * Verify that all required fields are filled out.
898 *
899 * @return boolean
900 */
901 Donation_Form.prototype.validate_required_fields = function() {
902 var has_all_required_fields = true;
903
904 this.get_required_fields().each( function() {
905 if ( '' === $( this ).find( 'input, select, textarea' ).val() ) {
906 has_all_required_fields = false;
907 }
908 });
909
910 if ( ! has_all_required_fields ) {
911 this.add_error( CHARITABLE_VARS.error_required_fields );
912 }
913
914 return has_all_required_fields;
915 };
916
917 /**
918 * Verifies the submission and returns true if it all looks ok.
919 *
920 * @param this.form The submitted form.
921 * @return boolean
922 */
923 Donation_Form.prototype.validate = function() {
924 /* First clear out the errors. */
925 this.clear_errors();
926
927 this.validate_amount();
928
929 this.validate_required_fields();
930
931 this.form.trigger( 'charitable:form:validate', this );
932
933 return this.errors.length === 0;
934 };
935
936 exports.Donation_Form = Donation_Form;
937
938 })( CHARITABLE, jQuery );
939
940 /**
941 * Set up Toggle object.
942 */
943 ( function( exports, $ ){
944 var Toggle = function() {
945
946 /**
947 * Hide toggle target.
948 */
949 var hide_target = function() {
950 get_target( $(this) ).addClass( 'charitable-hidden' );
951 };
952
953 /**
954 * Get the target element.
955 */
956 var get_target = function( $el ) {
957 var target = $el.data('charitable-toggle');
958
959 if ( target[0] !== '.' && target[0] !== '#' ) {
960 target = '#' + target;
961 }
962
963 return $( target );
964 }
965
966 /**
967 * Toggle event handler for any fields with the [data-charitable-toggle] attribute.
968 *
969 * @access private
970 */
971 var on_toggle = function() {
972 var $this = $( this ),
973 $target = get_target( $this );
974
975 $target.toggleClass( 'charitable-hidden', ( function(){
976 if ( $this.is( ':checkbox' ) ) {
977 return ! $this.is( ':checked' );
978 }
979 return false === $target.hasClass( 'charitable-hidden' );
980 })()
981 );
982
983 return false;
984 };
985
986 /**
987 * Initializing function.
988 */
989 var init = function() {
990 $( '[data-charitable-toggle]' ).each( hide_target );
991 };
992
993 // Initialization only required once.
994 $( 'body' ).on( 'click', '[data-charitable-toggle]', on_toggle );
995
996 // Check whether content is being loaded from the session.
997 if ( exports.content_loading ) {
998 var timer = setInterval(function(){
999 if ( false === exports.content_loading ) {
1000 init();
1001 clearInterval(timer);
1002 }
1003 }, 500);
1004 }
1005
1006 // Initialization that will be performed everytime
1007 return init;
1008 }
1009
1010 exports.Toggle = Toggle();
1011
1012 })( CHARITABLE, jQuery );
1013
1014
1015 /**
1016 * Do a version check.
1017 *
1018 * @since 1.6.19
1019 *
1020 * @param string version The version we are comparing with.
1021 * @param string compare If compare is left empty, use the Charitable version.
1022 * @return integer|boolean
1023 */
1024 CHARITABLE.VersionCompare = function( version, compare ) {
1025 compare = compare || CHARITABLE_VARS.version;
1026
1027 if ( typeof version + typeof compare != 'stringstring')
1028 return false;
1029
1030 var a = version.split( '.' ),
1031 b = compare.split( '.' ),
1032 i = 0,
1033 len = Math.max( a.length, b.length );
1034
1035 for ( ; i < len; i++ ) {
1036 if ((a[i] && !b[i] && parseInt(a[i]) > 0) || (parseInt(a[i]) > parseInt(b[i]))) {
1037 return 1;
1038 } else if ((b[i] && !a[i] && parseInt(b[i]) > 0) || (parseInt(a[i]) < parseInt(b[i]))) {
1039 return -1;
1040 }
1041 }
1042
1043 return 0;
1044 };
1045
1046 /**
1047 * Set up ResetSuggestedDonations object.
1048 */
1049 ( function( exports, $ ){
1050
1051 CHARITABLE.ResetSuggestedDonations = function() {
1052
1053 if ( $('.charitable-donation-options').length = 0 ) {
1054 // the options probably aren't on this page.
1055 return false;
1056 }
1057
1058 $( 'ul.donation-amounts input[type="radio"]' ).each( function() {
1059 if ( $( this ).is(':checked') ) {
1060 $( this ).closest( "li" ).addClass( "selected" );
1061 } else {
1062 $( this ).closest( "li" ).removeClass( "selected" );
1063 }
1064 });
1065
1066 }
1067
1068 })( CHARITABLE, jQuery );
1069
1070 /***
1071 * Finally queue up all the scripts.
1072 */
1073 ( function( $ ) {
1074
1075 /**
1076 * Prevent re-initializing form handlers.
1077 */
1078 CHARITABLE.forms_initialized = false;
1079
1080 $( document ).ready( function() {
1081
1082 $( 'html' ).addClass( 'js' );
1083
1084 var forms = [];
1085
1086 $( '.charitable-donation-form' ).each( function() {
1087 forms.push( new CHARITABLE.Donation_Form( $( this ) ) );
1088 });
1089
1090 CHARITABLE.Toggle();
1091
1092 CHARITABLE.ResetSuggestedDonations();
1093 });
1094
1095 })( jQuery );
1096