PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.6.7
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.6.7
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 1.8.0 All 210 releases
charitable / assets / js / charitable.js

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

853 lines 22.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 * Form object.
22 *
23 * @access public
24 */
25 this.form = form;
26
27 /**
28 * Flag to allow processing to be paused (i.e. while something like Stripe is processing).
29 *
30 * @access public
31 */
32 this.pause_processing = false;
33
34 /**
35 * Flag to prevent the on_submit handler from sending multiple concurrent AJAX requests
36 *
37 * @access public
38 */
39 this.submit_processing = false;
40
41 var self = this;
42 var $body = $( 'body' );
43
44 /**
45 * Handle click of terms link.
46 *
47 * @access private
48 */
49 var on_click_terms = function() {
50 self.form.find( '.charitable-terms-text' ).addClass( 'active' );
51 return false;
52 };
53
54 /**
55 * Focus event handler on custom donation amount input field.
56 *
57 * @access private
58 */
59 var on_focus_custom_amount = function() {
60
61 $( this ).closest( 'li' ).trigger( 'click' ).find( 'input[name=donation_amount]' ).prop( 'checked', true ).trigger( 'change' );
62
63 self.form.off( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
64
65 $( this ).focus();
66
67 self.form.on( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
68
69 };
70
71 /**
72 * Focus event handler for changes to custom donation amount input field.
73 *
74 * @access private
75 */
76 var on_change_custom_donation_amount = function() {
77
78 var unformatted = self.unformat_amount( $( this ).val() );
79
80 if ( $.trim( unformatted ) > 0 ) {
81 $( this ).val( self.format_amount( unformatted ) );
82 }
83
84 };
85
86 /**
87 * Select a donation amount.
88 *
89 * @return void
90 */
91 var on_select_donation_amount = function() {
92
93 var $li = $( this ).closest( 'li' );
94
95 // Already selected, quit early to prevent focus/change loop
96 if ( $li.hasClass( 'selected' ) ) {
97 return;
98 }
99
100 $li.parents( '.charitable-donation-form' ).find( '.donation-amount.selected' ).removeClass( 'selected' );
101
102 $li.addClass( 'selected' );
103
104 if ( $li.hasClass( 'custom-donation-amount' ) ) {
105 $li.find( 'input.custom-donation-input' ).focus();
106 }
107 };
108
109 /**
110 * Change event handler for payment gateway selector.
111 *
112 * @access private
113 */
114 var on_change_payment_gateway = function() {
115
116 self.hide_inactive_payment_methods();
117
118 self.show_active_payment_methods( $(this).val() );
119
120 };
121
122 /**
123 * Submit event handler for donation form.
124 *
125 * @access private
126 */
127 var on_submit = function( event ) {
128
129 var helper = event.data.helper;
130
131 if ( helper.submit_processing ) {
132 return false;
133 }
134
135 helper.submit_processing = true;
136
137 /* Display the processing spinner and hide the button */
138 helper.show_processing();
139
140 /* Validate the form submission before going further. */
141 if ( false === helper.validate() ) {
142
143 helper.hide_processing();
144
145 helper.print_errors();
146
147 helper.scroll_to_top();
148
149 return false;
150
151 }
152
153 /* If processing has been paused, return false now. */
154 if ( false !== helper.pause_processing ) {
155 return false;
156 }
157
158 /* If we're not using AJAX to process the donation further, return now. */
159 if ( 1 !== helper.form.data( 'use-ajax' ) ) {
160 return true;
161 }
162
163 /* If we're still here, trigger the processing event. */
164 $body.trigger( 'charitable:form:process', helper );
165
166 return false;
167
168 };
169
170 /**
171 * Process the donation.
172 *
173 * This is a callback for the 'charitable:form:process' event. It's called
174 * after validation has taken place.
175 *
176 * @param object Event
177 * @param object Donation_Form
178 */
179 var process_donation = function( event, helper ) {
180
181 var data = helper.get_data();
182 var form = helper.form;
183
184 /* Cancel the default Charitable action, but pass it along as the form_action variable */
185 data.action = 'make_donation';
186 data.form_action = data.charitable_action;
187 delete data.charitable_action;
188
189 $.ajax({
190 type: "POST",
191 data: data,
192 dataType: "json",
193 url: CHARITABLE_VARS.ajaxurl,
194 timeout: 0,
195 xhrFields: {
196 withCredentials: true
197 },
198 success: function (response) {
199
200 if ( response.success ) {
201 window.location.href = response.redirect_to;
202 }
203 else {
204 helper.hide_processing();
205
206 helper.print_errors( response.errors );
207
208 helper.scroll_to_top();
209
210 if ( response.donation_id ) {
211 helper.set_donation_id( response.donation_id );
212 }
213 }
214 }
215
216 }).fail(function (response, textStatus, errorThrown) {
217
218 if ( window.console && window.console.log ) {
219 console.log( response );
220 }
221
222 helper.hide_processing();
223
224 helper.print_errors( [ CHARITABLE_VARS.error_unknown ] );
225
226 helper.scroll_to_top();
227
228 });
229
230 return false;
231
232 }
233
234 /**
235 * Set up event handlers for donation forms.
236 *
237 * @return void
238 */
239 var init = function() {
240
241 // Init donation amount selection
242 self.form.on( 'click', '.donation-amount', on_select_donation_amount );
243 self.form.on( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
244 self.form.on( 'click', '.charitable-terms-link', on_click_terms );
245
246 // Init currency formatting
247 self.form.on( 'blur', '.custom-donation-input', on_change_custom_donation_amount );
248
249 self.form.find( '.donation-amount input:checked' ).each( function() {
250 $( this ).closest( 'li' ).addClass( 'selected' );
251 });
252
253 if ( self.get_all_payment_methods().length ) {
254 self.hide_inactive_payment_methods();
255 self.form.on( 'change', '#charitable-gateway-selector input[name=gateway]', on_change_payment_gateway );
256 }
257
258 // Handle donation form submission
259 self.form.on( 'submit', {
260 helper : self,
261 }, on_submit );
262
263 // This only fires after the first form instance is initialized.
264 if ( false === CHARITABLE.forms_initialized ) {
265
266 // Process the donation on the 'charitable:form:process' event.
267 $body.on( 'charitable:form:process', process_donation );
268
269 $body.trigger( 'charitable:form:initialize', self );
270
271 CHARITABLE.forms_initialized = true;
272 }
273
274 $body.trigger( 'charitable:form:loaded', self );
275 }
276
277 init();
278
279 };
280
281 /**
282 * Return a particular input field.
283 *
284 * @param string The field name to retrieve.
285 * @return object
286 */
287 Donation_Form.prototype.get_input = function( field ) {
288 return this.form.find( '[name=' + field + ']' );
289 }
290
291 /**
292 * Return the submitted email address.
293 *
294 * @return string
295 */
296 Donation_Form.prototype.get_email = function() {
297 return this.form.find( '[name=email]' ).val();
298 };
299
300 /**
301 * Get the chosen suggested amount.
302 *
303 * Note that when no option has been selected, or a custom donation amount has been added,
304 * this will return 0.
305 *
306 * @since 1.4.19
307 *
308 * @return float
309 */
310 Donation_Form.prototype.get_suggested_amount = function() {
311 return accounting.unformat(
312 this.form.find( '[name=donation_amount]:checked, input[type=hidden][name=donation_amount]' ).val(),
313 CHARITABLE_VARS.currency_format_decimal_sep
314 );
315 }
316
317 /**
318 * Get the custom amount.
319 *
320 * Note that this will return 0 when no custom amount has been entered or a 0 has been entered.
321 *
322 * @since 1.4.19
323 *
324 * @return float
325 */
326 Donation_Form.prototype.get_custom_amount = function() {
327 var input = this.form.find( '.charitable-donation-options.active .custom-donation-input' );
328
329 if ( 0 === input.length ) {
330 input = this.form.find( '.custom-donation-input' );
331 }
332
333 return accounting.unformat(
334 input.val(),
335 CHARITABLE_VARS.currency_format_decimal_sep
336 );
337 }
338
339 /**
340 * Get the submitted amount, taking into account both the custom & suggested donation fields.
341 *
342 * @return float
343 */
344 Donation_Form.prototype.get_amount = function() {
345 return this.get_suggested_amount() || this.get_custom_amount();
346 };
347
348 /**
349 * Get a description of the donation.
350 *
351 * @return string
352 */
353 Donation_Form.prototype.get_description = function() {
354 return this.form.find( '[name=description]' ).val() || '';
355 };
356
357 /**
358 * Get credit card number.
359 *
360 * @return string
361 */
362 Donation_Form.prototype.get_cc_number = function() {
363 return this.form.find( '#charitable_field_cc_number input' ).val() || '';
364 };
365
366 /**
367 * Get credit card CVC number.
368 *
369 * @return string
370 */
371 Donation_Form.prototype.get_cc_cvc = function() {
372 return this.form.find( '#charitable_field_cc_cvc input' ).val() || '';
373 };
374
375 /**
376 * Get credit card expiry month.
377 *
378 * @return string
379 */
380 Donation_Form.prototype.get_cc_expiry_month = function() {
381 return this.form.find( '#charitable_field_cc_expiration select.month' ).val() || '';
382 };
383
384 /**
385 * Get credit card expiry year.
386 *
387 * @return string
388 */
389 Donation_Form.prototype.get_cc_expiry_year = function() {
390 return this.form.find( '#charitable_field_cc_expiration select.year' ).val() || '';
391 };
392
393 /**
394 * Clear credit card fields.
395
396 *
397 * This is used by gateways that create tokens through Javascript (such as Stripe), to
398 * avoid credit card details hitting the server.
399 *
400 * @return void
401 */
402 Donation_Form.prototype.clear_cc_fields = function() {
403 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' );
404 };
405
406 /**
407 * Return the selected payment method.
408 *
409 * @return string
410 */
411 Donation_Form.prototype.get_payment_method = function() {
412 return this.form.find( '[type=hidden][name=gateway], [name=gateway]:checked' ).val() || '';
413 };
414
415 /**
416 * Return all payment methods.
417 *
418 * @return object
419 */
420 Donation_Form.prototype.get_all_payment_methods = function() {
421 return this.form.find( '#charitable-gateway-selector input[name=gateway]' );
422 }
423
424 /**
425 * Hide inactive payment methods.
426 *
427 * @return void
428 */
429 Donation_Form.prototype.hide_inactive_payment_methods = function() {
430 var active = this.get_payment_method();
431 var fields = this.form.find( '.charitable-gateway-fields[data-gateway!=' + active + ']' );
432
433 fields.hide();
434 fields.find( 'input[required],select[required],textarea[required]' ).attr( 'data-required', 1 ).attr( 'required', false );
435 };
436
437 /**
438 * Show active payment methods.
439 *
440 * @return void
441 */
442 Donation_Form.prototype.show_active_payment_methods = function( active ) {
443 var active = active || this.get_payment_method();
444 var fields = this.form.find( '.charitable-gateway-fields[data-gateway=' + active + ']' );
445
446 fields.show();
447 fields.find( '[data-required]' ).attr( 'required', true );
448 };
449
450 /**
451 * Select a donation amount.
452 *
453 * @param int price
454 * @param string symbol
455 * @return string
456 */
457 Donation_Form.prototype.format_amount = function( price, symbol ){
458 if ( typeof symbol === 'undefined' )
459 symbol = '';
460
461 return accounting.formatMoney( price, {
462 symbol : symbol,
463 decimal : CHARITABLE_VARS.currency_format_decimal_sep,
464 thousand: CHARITABLE_VARS.currency_format_thousand_sep,
465 precision : CHARITABLE_VARS.currency_format_num_decimals,
466 format: CHARITABLE_VARS.currency_format
467 }).trim();
468 };
469
470 /**
471 * Select a donation amount.
472 *
473 * @param int price
474 * @return string
475 */
476 Donation_Form.prototype.unformat_amount = function( price ) {
477 return Math.abs( parseFloat( accounting.unformat( price, CHARITABLE_VARS.currency_format_decimal_sep ) ) );
478 };
479
480 /**
481 * Add an error message.
482 *
483 * @param string message
484 * @return void
485 */
486 Donation_Form.prototype.add_error = function( message ) {
487 this.errors.push( message );
488 };
489
490 /**
491 * Return the errors.
492 *
493 * @return []
494 */
495 Donation_Form.prototype.get_errors = function() {
496 return this.errors;
497 };
498
499 /**
500 * Prints out a nice string describing the errors.
501 *
502 * @return string
503 */
504 Donation_Form.prototype.get_error_message = function() {
505 return this.errors.join( ' ' );
506 };
507
508 /**
509 * Print the errors at the top of the form.
510 *
511 * @param array
512 */
513 Donation_Form.prototype.print_errors = function( errors ) {
514
515 var e = errors || this.errors,
516 i = 0,
517 count = e.length,
518 output = '';
519
520 if ( 0 === count ) {
521 return;
522 }
523
524 if ( this.form.find( '.charitable-form-errors' ).length ) {
525 this.form.find( '.charitable-form-errors' ).remove();
526 }
527
528 output += '<div class="charitable-form-errors charitable-notice"><ul class="errors"><li>';
529 output += e.join( '</li><li>' );
530 output += '</li></ul></div>';
531
532 this.form.prepend( output );
533
534 }
535
536 /**
537 * Clear the errors and remove the printed errors.
538 */
539 Donation_Form.prototype.clear_errors = function() {
540
541 this.errors = [];
542
543 if ( this.form.find( '.charitable-form-errors' ).length ) {
544 this.form.find( '.charitable-form-errors' ).remove();
545 }
546
547 }
548
549 /**
550 * Show that the donation form is processing.
551 */
552 Donation_Form.prototype.show_processing = function() {
553 this.form.find( '.charitable-form-processing' ).show();
554 this.form.find( 'button[name="donate"]' ).hide();
555 }
556
557 /**
558 * Hide the processing spinner and show the donate button.
559 */
560 Donation_Form.prototype.hide_processing = function() {
561 this.form.find( '.charitable-form-processing' ).hide();
562 this.form.find( 'button[name="donate"]' ).show();
563
564 this.submit_processing = false;
565 this.pause_processing = false;
566 }
567
568 /**
569 * Scroll to the top of the form.
570 */
571 Donation_Form.prototype.scroll_to_top = function() {
572
573 var $modal = this.form.parents( '.charitable-modal' );
574
575 if ( $modal.length ) {
576 $modal.scrollTop( 0 );
577 }
578 else {
579 window.scrollTo( this.form.position().left, this.form.position().top );
580 }
581
582 };
583
584 /**
585 * Set the donation ID.
586 */
587 Donation_Form.prototype.set_donation_id = function( donation_id ) {
588 this.form.find( '[name=ID]' ).val( donation_id );
589 }
590
591 /**
592 * Returns all of the submitted data as key=>value object.
593 *
594 * @return object
595 */
596 Donation_Form.prototype.get_data = function() {
597
598 return this.form.serializeArray().reduce( function( obj, item ) {
599 if ( '[]' === item.name.slice( -2 ) ) {
600 var name = item.name.slice( 0, -2 );
601
602 if ( ! obj.hasOwnProperty( name ) ) {
603 obj[name] = [];
604 }
605
606 obj[name].push( item.value );
607 } else {
608 obj[ item.name ] = item.value;
609 }
610
611 return obj;
612 }, {} );
613
614 };
615
616 /**
617 * Returns all of the required fields.
618 *
619 * @return object
620 */
621 Donation_Form.prototype.get_required_fields = function() {
622
623 var fields = this.form.find( '.charitable-fieldset .required-field' ).not( '#charitable-gateway-fields .required-field' ),
624 method = this.get_payment_method();
625
626 if ( '' !== method ) {
627
628 var gateway_fields = this.form.find( '[data-gateway=' + method + '] .required-field' );
629
630 if ( gateway_fields.length ) {
631 fields = $.merge( fields, gateway_fields );
632 }
633
634 }
635
636 return fields;
637
638 };
639
640 /**
641 * Make sure that the submitted amount is valid.
642 *
643 * @return boolean
644 */
645 Donation_Form.prototype.is_valid_amount = function() {
646
647 var minimum = parseFloat( CHARITABLE_VARS.minimum_donation );
648
649 return minimum > 0 ? this.get_amount() >= minimum : this.get_amount() >= minimum;
650
651 };
652
653 /**
654 * Validate the amount. If not valid, set an error.
655 *
656 * @return boolean
657 */
658 Donation_Form.prototype.validate_amount = function() {
659
660 if ( false === this.is_valid_amount() ) {
661 this.add_error( CHARITABLE_VARS.error_invalid_amount );
662 return false;
663 }
664
665 return true;
666
667 };
668
669 /**
670 * Verify that all required fields are filled out.
671 *
672 * @return boolean
673 */
674 Donation_Form.prototype.validate_required_fields = function() {
675
676 var has_all_required_fields = true;
677
678 var required = this.get_required_fields();
679
680 this.get_required_fields().each( function() {
681
682 if ( '' === $( this ).find( 'input, select, textarea' ).val() ) {
683 has_all_required_fields = false;
684 }
685
686 });
687
688 if ( ! has_all_required_fields ) {
689 this.add_error( CHARITABLE_VARS.error_required_fields );
690 }
691
692 return has_all_required_fields;
693
694 };
695
696 /**
697 * Verifies the submission and returns true if it all looks ok.
698 *
699 * @param this.form The submitted form.
700 * @return boolean
701 */
702 Donation_Form.prototype.validate = function() {
703
704 /* First clear out the errors. */
705 this.clear_errors();
706
707 this.validate_amount();
708
709 this.validate_required_fields();
710
711 this.form.trigger( 'charitable:form:validate', this );
712
713 return this.errors.length === 0;
714
715 };
716
717 exports.Donation_Form = Donation_Form;
718
719 })( CHARITABLE, jQuery );
720
721 /**
722 * Set up Toggle object.
723 */
724 ( function( exports, $ ){
725
726 var Toggle = function() {
727
728 /**
729 * Hide toggle target.
730 */
731 var hide_target = function() {
732 get_target( $(this) ).addClass( 'charitable-hidden' );
733 };
734
735 /**
736 * Get the target element.
737 */
738 var get_target = function( $el ) {
739 var target = $el.data('charitable-toggle');
740
741 if ( target[0] !== '.' && target[0] !== '#' ) {
742 target = '#' + target;
743 }
744
745 return $( target );
746 }
747
748 /**
749 * Toggle event handler for any fields with the [data-charitable-toggle] attribute.
750 *
751 * @access private
752 */
753 var on_toggle = function() {
754 var $this = $( this ),
755 $target = get_target( $this );
756
757 $target.toggleClass( 'charitable-hidden', ( function(){
758 if ( $this.is( ':checkbox' ) ) {
759 return ! $this.is( ':checked' );
760 }
761 return false === $target.hasClass( 'charitable-hidden' );
762 })()
763 );
764
765 return false;
766 };
767
768 /**
769 * Initializing function.
770 */
771 var init = function() {
772 $( '[data-charitable-toggle]' ).each( hide_target );
773 };
774
775 // Initialization only required once.
776 $( 'body' ).on( 'click', '[data-charitable-toggle]', on_toggle );
777
778 // Check whether content is being loaded from the session.
779 if ( exports.content_loading ) {
780 var timer = setInterval(function(){
781 if ( false === exports.content_loading ) {
782 init();
783 clearInterval(timer);
784 }
785 }, 500);
786 }
787
788 // Initialization that will be performed everytime
789 return init;
790 }
791
792 exports.Toggle = Toggle();
793
794 })( CHARITABLE, jQuery );
795
796 /**
797 * Set up Charitable helper functions.
798 */
799 ( function( exports, $ ) {
800
801 var Helpers = function() {
802
803 /**
804 * Sanitize URLs.
805 */
806 this.sanitize_url = function( input ) {
807
808 var url = input.value.toLowerCase();
809
810 if ( !/^https?:\/\//i.test( url ) && url.length > 0 ) {
811 url = 'http://' + url;
812
813 input.value = url;
814 }
815 }
816
817 };
818
819 })( CHARITABLE, jQuery );
820
821 /**
822 * URL sanitization.
823 *
824 * This is provided for backwards compatibility.
825 */
826 CHARITABLE.SanitizeURL = function( input ) {
827 CHARITABLE.Helpers.sanitize_url( input );
828 };
829
830 /***
831 * Finally queue up all the scripts.
832 */
833 ( function( $ ) {
834
835 /**
836 * Prevent re-initializing form handlers.
837 */
838 CHARITABLE.forms_initialized = false;
839
840 $( document ).ready( function() {
841
842 $( 'html' ).addClass( 'js' );
843
844 $( '.charitable-donation-form' ).each( function() {
845 new CHARITABLE.Donation_Form( $( this ) );
846 });
847
848 CHARITABLE.Toggle();
849
850 });
851
852 })( jQuery );
853