1/** 2 * WooCommerce Custom Fields Plugin General Scripts 3 */ 4jQuery(document).ready(function() { 5 6 // Track if customer id cookie was set 7 var customer_id_cookie_set = false; 8 9 // Checkout color picker handlers 10 var checkout_color_picker_handlers = {}; 11 12 // Character limit flash control 13 var character_limit_flashing = false; 14 15 /** 16 * Ajax request delay function 17 */ 18 var delay_while_typing = (function(){ 19 var timer = 0; 20 return function(callback, ms) { 21 clearTimeout(timer); 22 timer = setTimeout(callback, ms); 23 }; 24 })(); 25 26 /** 27 * Limit multiselect or checkbox selections 28 */ 29 jQuery('select[data-wccf-max-selected] option').click(function() { 30 31 // Get selected options 32 var select_field = jQuery(this).parent(); 33 var selected_options = select_field.find('option:selected'); 34 35 // Get counts 36 var selected_option_count = selected_options.length; 37 var max_selected = select_field.data('wccf-max-selected'); 38 var difference = selected_option_count - max_selected; 39 40 // Limit reached 41 if (selected_option_count > max_selected) { 42 43 // Remove last 44 jQuery(this).removeAttr('selected'); 45 46 // Limit is still reached (selection made while holding SHIFT) 47 if (difference > 1) { 48 for (var i = 1; i < difference; i++) { 49 select_field.find('option:selected').first().removeAttr('selected'); 50 } 51 } 52 } 53 }); 54 jQuery('input[data-wccf-max-selected]').change(function() { 55 56 var name = jQuery(this).prop('name'); 57 58 // Get checked checkboxes 59 var checked_checkboxes = jQuery(this).closest('.wccf_field_container, .wccf_meta_box_field_container, .wccf_user_profile_field_container').find('input[name="' + name + '"]:checked'); 60 61 // Limit reached 62 if (checked_checkboxes.length > jQuery(this).data('wccf-max-selected')) { 63 this.checked = false; 64 } 65 }); 66 67 /** 68 * Frontend setup 69 */ 70 if (!wccf_general_config.is_backend) { 71 72 /** 73 * Dynamic extra fees on checkout 74 */ 75 jQuery('form .wccf_checkout_field').each(function() { 76 77 // Check if field has pricing 78 if (!jQuery(this).data('wccf-checkout-pricing')) { 79 return; 80 } 81 82 // Handle field value change 83 jQuery(this).change(function(e) { 84 85 // Trigger checkout refresh but only if this is not a file field (handled separately) 86 if (jQuery(this).prop('type') !== 'file') { 87 jQuery('body').trigger('update_checkout'); 88 } 89 }); 90 }); 91 92 /** 93 * Toggle character limit 94 */ 95 jQuery('.wccf[data-wccf-character-limit]').each(function() { 96 set_up_character_limit_for_field(jQuery(this)); 97 }); 98 99 /** 100 * Set customer id cookie if none exists yet 101 */ 102 jQuery('.wccf_field_container input.wccf_file').first().each(function() { 103 maybe_set_customer_id_cookie(); 104 }); 105 106 /** 107 * Maybe reload fields via Ajax on variation change 108 */ 109 if (jQuery('form.variations_form div[data-wccf-uses-attribute-conditions]').length) { 110 jQuery('.variations_form').on('woocommerce_variation_has_changed', function() { 111 refresh_product_fields_view(); 112 }).closest('form.cart').on('found_variation', function() { 113 refresh_product_fields_view(); 114 }); 115 } 116 117 /** 118 * Maybe reload fields via Ajax on quantity change 119 */ 120 jQuery('form.cart input[name="quantity"]').each(function() { 121 122 // Quantity change 123 jQuery(this).change(function() { 124 handle_product_quantity_change(jQuery(this)); 125 }); 126 127 // Initial setup 128 if (jQuery(this).val() > 1) { 129 handle_product_quantity_change(jQuery(this)); 130 } 131 }); 132 } 133 134 /** 135 * Set up character limit for single field 136 */ 137 function set_up_character_limit_for_field(field) 138 { 139 var character_limit_element = field.closest('.wccf_field_container').find('.wccf_character_limit'); 140 141 // Show or hide character limit 142 if (character_limit_element.length) { 143 field.focus(function() { 144 character_limit_element.fadeIn(); 145 }); 146 field.focusout(function() { 147 character_limit_element.fadeOut(); 148 }); 149 } 150 151 // Update character limit 152 field.keyup(function() { 153 update_characters_remaining(jQuery(this)); 154 }); 155 field.change(function() { 156 update_characters_remaining(jQuery(this)); 157 }); 158 update_characters_remaining(field); 159 160 // Prevent form submit 161 field.closest('form').submit(function(e) { 162 if (field.val().length > field.data('wccf-character-limit')) { 163 field.focus(); 164 e.preventDefault(); 165 } 166 }); 167 } 168 169 /** 170 * Update characters remaining 171 */ 172 function update_characters_remaining(field) 173 { 174 175 // Select character limit element 176 var character_limit_element = field.closest('.wccf_field_container').find('.wccf_character_limit'); 177 178 // Check if character limit element exists 179 if (character_limit_element.length) { 180 181 // Get character limit value 182 var limit = field.data('wccf-character-limit'); 183 184 // Get number of remaining character 185 var remaining = limit - field.val().length; 186 187 // Update value 188 character_limit_element.find('.wccf_characters_remaining').html(remaining); 189 190 // Over limit 191 if (remaining < 0) { 192 193 // Set over quota class 194 character_limit_element.addClass('wccf_character_limit_over_quota'); 195 196 // Flash element twice 197 if (!character_limit_flashing) { 198 character_limit_flashing = true; 199 character_limit_element.fadeOut(100).fadeIn(100).fadeOut(100).fadeIn(100, function() { 200 character_limit_flashing = false;
201 }); 202 } 203 } 204 // Not over limit 205 else { 206 207 // Unset over quota class 208 character_limit_element.removeClass('wccf_character_limit_over_quota'); 209 } 210 } 211 } 212 213 /** 214 * Set customer id cookie if none exists yet 215 */ 216 function maybe_set_customer_id_cookie() 217 { 218 // Check if cookie is already set 219 if (customer_id_cookie_set) { 220 return; 221 } 222 223 // Send request to retrieve customer id cookie data 224 jQuery.get( 225 wccf_general_config.ajaxurl, 226 { 227 action: 'wccf_get_customer_id_cookie_data' 228 }, 229 function(response) { 230 231 // Parse response 232 response = parse_ajax_json_response(response); 233 234 // Error occurred 235 if (typeof response !== 'object' || typeof response.result === 'undefined' || response.result !== 'success' || typeof response.data !== 'object') { 236 return; 237 } 238 239 // Get cookie data 240 var cookie_data = response.data; 241 242 // Cookie already exists 243 if (cookie_exists(cookie_data.name)) { 244 return; 245 } 246 247 // Set customer id cookie 248 set_cookie(cookie_data); 249 } 250 ); 251 252 customer_id_cookie_set = true; 253 } 254 255 /** 256 * Check if cookie exists 257 */ 258 function cookie_exists(cookie_name) 259 { 260 return document.cookie.indexOf(cookie_name + '=') !== -1; 261 } 262 263 /** 264 * Set cookie 265 */ 266 function set_cookie(cookie_data) 267 { 268 // Generate cookie string 269 var cookie = cookie_data.name + '=' + cookie_data.value + '; expires=' + cookie_data.expiration + '; path=' + cookie_data.path; 270 271 // Optionally make cookie secure 272 if (cookie_data.secure) { 273 cookie += '; secure'; 274 } 275 276 // Optionally set cookie domain 277 if (cookie_data.domain) { 278 cookie += '; domain=' + cookie_data.domain; 279 } 280 281 // Set cookie 282 document.cookie = cookie; 283 } 284 285 /** 286 * Maybe reload fields via Ajax on quantity change 287 */ 288 function handle_product_quantity_change(quantity_field) { 289 if (quantity_field.closest('form.cart').find('.wccf[data-wccf-quantity-based]').length) { 290 refresh_product_fields_view(); 291 } 292 } 293 294 /** 295 * Refresh product fields view 296 */ 297 function refresh_product_fields_view() 298 { 299 300 // Take reference of the form 301 var form = jQuery('form.cart'); 302 303 // Block UI until we update fields 304 form.block({ 305 message: null, 306 overlayCSS: { 307 background: '#fff', 308 opacity: 0.6 309 } 310 }); 311 312 // Prepare data 313 var form_data = form.serialize(); 314 315 // Add product id 316 form.find('button[type="submit"][name="add-to-cart"]').each(function() { 317 318 var product_id = jQuery(this).val(); 319 320 if (product_id) { 321 form_data += (form_data !== '' ? '&' : '') + 'rightpress_reference_product_id=' + product_id; 322 } 323 }); 324 325 // Update product fields 326 jQuery.post( 327 wccf_general_config.ajaxurl, 328 { 329 action: 'wccf_refresh_product_field_view', 330 data: form_data 331 }, 332 function(response) { 333 334 // Parse response 335 response = parse_ajax_json_response(response); 336 337 // Error occurred or response is malformed 338 if (typeof response !== 'object' || typeof response.result === 'undefined' || response.result !== 'success' || typeof response.fields !== 'object') { 339 alert(wccf_general_config.messages.error_reload_notice + ' ' + wccf_general_config.messages.page_reload); 340 location.reload(); 341 return; 342 } 343 344 // Take reference of old container 345 var old_container = jQuery('div#wccf_product_field_master_container'); 346 347 // Check if we have any fields 348 if (response.fields.length) { 349 350 // Insert hidden new container before old container 351 old_container.before('<div id="wccf_product_field_master_container_
351copy" style="display: none;"></div>'); 352 353 // Take reference of new container 354 var new_container = jQuery('div#wccf_product_field_master_container_copy'); 355 356 // Iterate over fields 357 jQuery.each(response.fields, function(index, field) { 358 359 // Move existing field from old container to new container (detach method copies all events too) 360 if (old_container.find('#' + field.element_id).length) { 361 old_container.find('#' + field.element_id).closest('div.wccf_field_container').detach().appendTo(new_container); 362 } 363 // Special handling for existing checkbox and radio button sets 364 else if (['checkbox', 'radio'].indexOf(field.field_type) !== -1 && old_container.find('input[name="' + field.element_name + '"]').length) { 365 old_container.find('input[name="' + field.element_name + '"]').closest('div.wccf_field_container').detach().appendTo(new_container); 366 } 367 // Field does not exist 368 else { 369 370 // Print new field 371 new_container.append(field.html); 372 373 // Take reference of new field element 374 var new_field = new_container.find('.wccf_field_container').last(); 375 376 // Set up field functionality after dynamic field view update 377 set_up_field_functionality_after_view_update(new_field, form, false); 378 379 // Set up live product update 380 form.trigger('rightpress_live_product_update_attach_input', new_field); 381 } 382 }); 383 384 // Remove old container along with no longer needed fields 385 old_container.remove(); 386 387 // Change id of new container 388 jQuery('div#wccf_product_field_master_container_copy').prop('id', 'wccf_product_field_master_container').fadeIn(); 389 } 390 // No fields returned 391 else { 392 393 // Remove all fields from old container 394 old_container.empty(); 395 } 396 397 // Recheck frontend conditions 398 wccf_check_frontend_conditions(form); 399 400 // Trigger live product update 401 form.trigger('rightpress_live_product_update_trigger'); 402 403 // Unblock UI 404 form.unblock(); 405 } 406 ); 407 } 408 409 /** 410 * Set up field functionality after dynamic field view update 411 */ 412 function set_up_field_functionality_after_view_update(field, form, preserve_existing_files_on_server) 413 { 414 415 // Set up character limit 416 field.find('.wccf[data-wccf-character-limit]').each(function() { 417 set_up_character_limit_for_field(jQuery(this)); 418 }); 419 420 // Set customer id cookie if not exists 421 field.find('input.wccf_file').first().each(function() { 422 maybe_set_customer_id_cookie(); 423 }); 424 425 // Set up date/time pickers 426 jQuery.each(['date', 'time', 'datetime'], function(index, type) { 427 field.find('.wccf_' + type).each(function() { 428 set_up_datetimepicker(jQuery(this), type); 429 }); 430 }); 431 432 // Set up color picker 433 field.find('.wccf_color').each(function() { 434 set_up_color_picker(jQuery(this)); 435 }); 436 437 // Set up frontend condition checks for this element 438 field.find('.wccf').each(function() { 439 wccf_initialize_frontend_condition_checks(jQuery(this), form); 440 }); 441 442 // Set up file uploads 443 field.find('.wccf_file').each(function() { 444 wccf_set_up_file_upload_handler_for_field(jQuery(this), preserve_existing_files_on_server); 445 }); 446 } 447 448 /** 449 * Date/time pickers 450 */ 451 jQuery.each(['date', 'time', 'datetime'], function(index, type) { 452 jQuery('.wccf_' + type).each(function() { 453 set_up_datetimepicker(jQuery(this), type); 454 }); 455 }); 456 457 /** 458 * Color picker 459 */ 460 jQuery('.wccf_color').each(function() { 461 set_up_color_picker(jQuery(this)); 462 }); 463 464 /** 465 * Check frontend conditions 466 */ 467 function wccf_check_frontend_conditions(form) 468 { 469 470 var fields_with_pricing_displayed = false;
471 472 // Do not check frontend conditions in cart item product field editing form 473 if (form.prop('id') === 'wccf_cart_item_product_field_editing_form') { 474 return; 475 } 476 477 // Iterate over all custom fields in this form 478 form.find('.wccf').each(function() { 479 480 // Get element id and conditions variable name 481 var id = jQuery(this).prop('id'); 482 var conditions = 'wccf_conditions_' + id; 483 484 // Check if we have any conditions for this field 485 if (typeof window[conditions] !== 'undefined') { 486 487 // Track if all conditions match 488 var conditions_match = true; 489 490 // Reference context 491 var context = window[conditions]['context']; 492 493 // Iterate over conditions 494 for (var i = 0; i < window[conditions]['conditions'].length; i++) { 495 496 // Reference current condition and context 497 var condition = window[conditions]['conditions'][i]; 498 499 // Check if condition is matched 500 if (!check_condition(form, condition, context)) { 501 conditions_match = false; 502 } 503 } 504 505 // Show field 506 if (conditions_match) { 507 jQuery('#' + id).closest('.wccf_field_container, .wccf_meta_box_field_container, .wccf_user_profile_field_container').each(function() { 508 jQuery(this).fadeIn().find('input, select, textarea').removeAttr('disabled').each(function() { 509 if (typeof jQuery(this).data('wccf-pricing') !== 'undefined') { 510 fields_with_pricing_displayed = true; 511 } 512 }); 513 }); 514 } 515 // Hide field 516 else { 517 jQuery('#' + id).closest('.wccf_field_container, .wccf_meta_box_field_container, .wccf_user_profile_field_container').each(function() { 518 jQuery(this).fadeOut().find('input, select, textarea').attr('disabled', 'disabled'); 519 }); 520 } 521 } 522 }); 523 524 // Trigger live product update if needed 525 if (fields_with_pricing_displayed) { 526 form.trigger('rightpress_live_product_update_trigger'); 527 } 528 } 529 530 /** 531 * Check single frontend condition 532 */ 533 function check_condition(form, condition, context) 534 { 535 if (condition.type === 'other__other_custom_field') { 536 return check_condition_other_custom_field(form, condition, context); 537 } 538 539 return false; 540 } 541 542 /** 543 * Check other custom field condition 544 */ 545 function check_condition_other_custom_field(form, condition, context) 546 { 547 548 // Reference field that we are checking against 549 var other_field = form.find('.wccf_' + context + '[name="wccf[' + context + '][' + condition.other_field_id + ']"]:not(:disabled), .wccf_' + context + '[name="wccf_ignore[' + context + '][' + condition.other_field_id + ']"]:not(:disabled)'); 550 551 // Field not found? Maybe it accepts multiple values 552 if (!other_field.length) { 553 other_field = form.find('.wccf_' + context + '[name="wccf[' + context + '][' + condition.other_field_id + '][]"]:not(:disabled), .wccf_' + context + '[name="wccf_ignore[' + context + '][' + condition.other_field_id + '][]"]:not(:disabled)'); 554 } 555 556 // Reference method 557 var method = condition.method_option; 558 559 // Reference field not found on page 560 if (!other_field.length) { 561 562 // Negative conditions 563 if (['is_empty', 'does_not_contain', 'does_not_equal', 'is_not_checked'].indexOf(method) !== -1) { 564 return true; 565 } 566 // Positive conditions 567 else { 568 return false; 569 } 570 } 571 572 // Is Empty 573 if (method === 'is_empty') { 574 return is_empty(form, other_field); 575 } 576 577 // Is Not Empty 578 else if (method === 'is_not_empty') { 579 return !is_empty(form, other_field); 580 } 581 582 // Contains 583 else if (method === 'contains') { 584 return contains(form, other_field, condition.text); 585 } 586
587 // Does Not Contain 588 else if (method === 'does_not_contain') { 589 return !contains(form, other_field, condition.text); 590 } 591 592 // Equals 593 else if (method === 'equals') { 594 return equals(form, other_field, condition.text); 595 } 596 597 // Does Not Equal 598 else if (method === 'does_not_equal') { 599 return !equals(form, other_field, condition.text); 600 } 601 602 // Less Than 603 else if (method === 'less_than') { 604 return less_than(form, other_field, condition.text); 605 } 606 607 // Less Or Equal To 608 else if (method === 'less_or_equal_to') { 609 return !more_than(form, other_field, condition.text); 610 } 611 612 // More Than 613 else if (method === 'more_than') { 614 return more_than(form, other_field, condition.text); 615 } 616 617 // More Or Equal 618 else if (method === 'more_or_equal') { 619 return !less_than(form, other_field, condition.text); 620 } 621 622 // Is Checked 623 else if (method === 'is_checked') { 624 return is_checked(form, other_field); 625 } 626 627 // Is Not Checked 628 else if (method === 'is_not_checked') { 629 return !is_checked(form, other_field); 630 } 631 } 632 633 /** 634 * Check if field element is empty 635 */ 636 function is_empty(form, field) 637 { 638 // Get value of field that we are checking against 639 var field_value = get_value(form, field); 640 641 // Check if value is empty 642 return (field_value === '' || field_value === null || field_value.length === 0); 643 } 644 645 /** 646 * Check if field element contains string 647 */ 648 function contains(form, field, value) 649 { 650 // Get value of field that we are checking against 651 var field_value = get_value(form, field); 652 653 // Will check subscring in string and whole array value in array (for select fields) 654 if (field_value !== null && field_value.indexOf(value) > -1) { 655 return true; 656 } 657 658 return false; 659 } 660 661 /** 662 * Check if field element equals string 663 */ 664 function equals(form, field, value) 665 { 666 // Get value of field that we are checking against 667 var field_value = get_string_value(get_value(form, field)); 668 669 // Check if it equals given string 670 if (field_value !== false && field_value === value) { 671 return true; 672 } 673 674 return false; 675 } 676 677 /** 678 * Check if field element is less than value 679 */ 680 function less_than(form, field, value) 681 { 682 // Get value of field that we are checking against 683 var field_value = get_number_value(get_string_value(get_value(form, field))); 684 685 // Check if value is less than given value 686 if (field_value !== false && field_value < value) { 687 return true; 688 } 689 690 return false; 691 } 692 693 /** 694 * Check if field element is more than value 695 */ 696 function more_than(form, field, value) 697 { 698 // Get value of field that we are checking against 699 var field_value = get_number_value(get_string_value(get_value(form, field))); 700 701 // Check if value is less than given value 702 if (field_value !== false && field_value > value) { 703 return true; 704 } 705 706 return false; 707 } 708 709 /** 710 * Get number value from string value 711 */ 712 function get_number_value(value) 713 { 714 // Empty string equals zero 715 if (value === '') { 716 return 0; 717 } 718 719 // Try parsing value to float 720 var float_value = parseFloat(value); 721 722 // Value appears to be a number 723 if (!isNaN(float_value)) { 724 return float_value; 725 } 726 727 // Value does not appear to be a number 728 return false; 729 } 730 731 /** 732 * Check if field element is checked 733 */ 734 function is_checked(form, field) 735 { 736 // In case of radio or checkbox 737 if (field.is(':checkbox') || field.is(':radio')) { 738 if (field.is(':checked')) { 739 return true; 740 } 741 } 742 743 // In case of other fields - make sure it's not empty 744 else { 745 if (!is_empty(form, field)) { 746 return true; 747 } 748 } 749 750 return false;
751 } 752 753 /** 754 * Get value depending on field type 755 */ 756 function get_value(form, field) 757 { 758 // Special handling for checkboxes and radio buttons 759 if (field.is(':radio') || field.is(':checkbox')) { 760 761 // Store values 762 var values = []; 763 764 // Iterate over options and pick those that are checked 765 field.each(function() { 766 if (jQuery(this).is(':checked')) { 767 values.push(jQuery(this).val()); 768 } 769 }); 770 771 // Return values 772 return values.length > 0 ? values : ''; 773 } 774 // Special handling for file fields 775 else if (field.is(':file')) { 776 777 // Get uploaded files 778 var upload_list = field.closest('.wccf_field_container, .wccf_meta_box_field_container, .wccf_user_profile_field_container').find('.wccf_file_upload_list'); 779 780 // No uploaded files found 781 if (!upload_list.length) { 782 return ''; 783 } 784 785 // Store values 786 var values = []; 787 788 // Iterate over uploaded files 789 upload_list.each(function() { 790 if (jQuery(this).find('._' + field.prop('id')).length) { 791 var upload_value = jQuery(this).find('._' + field.prop('id')).val(); 792 793 if (upload_value) { 794 values.push(upload_value); 795 } 796 } 797 }); 798 799 // Return values 800 return values.length > 0 ? values : ''; 801 } 802 // Field selected successfully - return value 803 else { 804 return (typeof field.val() === 'string' ? field.val().trim() : field.val()); 805 } 806 } 807 808 /** 809 * Get string value from any value 810 */ 811 function get_string_value(value) 812 { 813 // In case of string 814 if (typeof value === 'string') { 815 return value.trim(); 816 } 817 818 // In case of array with single element 819 if (value !== null && typeof value === 'object' && value.length === 1 && typeof value[0] === 'string') { 820 return value[0].trim(); 821 } 822 823 return false; 824 } 825 826 /** 827 * Clear field value 828 */ 829 function clear_field_value(field) 830 { 831 if (field.is('select')) { 832 field.prop('selectedIndex', 0); 833 if (field.hasClass('rightpress_select2')) { 834 field.val('').change(); 835 } 836 } 837 else if (field.is(':radio, :checkbox')) { 838 field.removeAttr('checked'); 839 } 840 else { 841 field.val(''); 842 } 843 } 844 845 /** 846 * Initialize frontend condition checks 847 */ 848 function wccf_initialize_frontend_condition_checks(field, form) 849 { 850 // Key up 851 field.keyup(function() { 852 wccf_check_frontend_conditions(form); 853 }); 854 855 // Value change 856 field.change(function() { 857 wccf_check_frontend_conditions(form); 858 }); 859 } 860 jQuery('form:has(.wccf)').each(function() { 861 862 // Get reference 863 var form = jQuery(this); 864 865 // Check conditions on page load 866 wccf_check_frontend_conditions(form); 867 868 // Check conditions on interaction with elements 869 form.find('.wccf').each(function() { 870 wccf_initialize_frontend_condition_checks(jQuery(this), form); 871 }); 872 }); 873 874 /** 875 * Handle file uploads 876 */ 877 function wccf_set_up_file_upload_handler_for_field(field, preserve_existing_files_on_server) 878 { 879 880 // Get field container for current element 881 var field_container = field.closest('.wccf_field_container, .wccf_meta_box_field_container, .wccf_user_profile_field_container'); 882 var form = field.closest('form'); 883 884 // Get current element properties 885 var field_id = field.prop('id'); 886 var is_multiple = field.prop('multiple'); 887 888 // Prepare hidden field name and id 889 var hidden_field_class = '_' + field_id; 890 var hidden_field_name = field.prop('name').replace('wccf_ignore[', 'wccf['); 891 892 // Hidden field name must always accept multiple values so we get them in an array on server and processing is the same in all cases
893 if (hidden_field_name.indexOf('[]', (hidden_field_name.length - 2)) === -1) { 894 hidden_field_name += '[]'; 895 } 896 897 // Track progress of individual file uploads 898 var wccf_upload_progress_ids = []; 899 var wccf_upload_progress_id = 0; 900 901 // Keep track of uploaded file sizes for concurrent file upload validation 902 field_container.data('wccf_uploaded_file_sizes', {}); 903 904 // Define additional data to be passed to server 905 var additional_data = [{ 906 name: 'action', 907 value: 'wccf_file_upload' 908 }]; 909 910 // Add post ID if present 911 jQuery('form[name="post"] input[name="post_ID"], form#your-profile input[name="user_id"]').each(function() { 912 additional_data.push({ 913 name: 'item_id', 914 value: jQuery(this).val() 915 }); 916 }); 917 918 var jqXHR = null; 919 920 // Abort any previous uploads and reset upload progress file counter 921 field.change(function() { 922 if (jqXHR !== null) { 923 jqXHR.abort(); 924 } 925 }); 926 927 // Initialize file upload handler 928 field.fileupload({ 929 url: wccf_general_config.ajaxurl, 930 sequentialUploads: true, 931 type: 'POST', 932 formData: function () { 933 return additional_data.concat([{name: 'form_data', value: form.serialize()}]); 934 }, 935 dataType: 'json', 936 dataFilter: function(raw_response) { 937 return parse_ajax_json_response(raw_response, true); 938 }, 939 add: function (e, data) { 940 941 // Increment upload progress identifier 942 wccf_upload_progress_id++; 943 944 // Set identifier to data object 945 data.wccf_upload_progress_id = wccf_upload_progress_id; 946 wccf_upload_progress_ids.push(wccf_upload_progress_id); 947 948 // Clear previous file if multiple files are not allowed 949 if (!is_multiple) { 950 field_container.find('.wccf_file_upload_list').html(''); 951 field_container.find('.wccf_file_upload_status').remove(); 952 } 953 954 // Get message 955 var message = wccf_general_config.messages.file_uploading + ' ' + data.files[0].name; 956 957 // Append progress bar 958 field_container.find('input[type="file"]').before('<div class="wccf_file_upload_status wccf_file_upload_status_' + wccf_upload_progress_id + '"><small class="wccf_file_upload_message">' + message + '</small><small class="wccf_file_upload_progress_wrapper"><small class="wccf_file_upload_progress"></small></small></div>'); 959 960 // Start uploading 961 jqXHR = data.submit(); 962 }, 963 progress: function (e, data) { 964 965 // Select correct status element 966 var status_element = field_container.find('.wccf_file_upload_status_' + data.wccf_upload_progress_id); 967 968 // Get progress 969 var progress = parseInt(data.loaded / data.total * 100, 10); 970 971 // Update progress 972 status_element.find('small.wccf_file_upload_progress').css('width', progress + '%'); 973 }, 974 done: function (e, data) { 975 976 // Remove from tracked progress ids array 977 if (wccf_upload_progress_ids.indexOf(data.wccf_upload_progress_id) !== -1) { 978 wccf_upload_progress_ids.splice(wccf_upload_progress_ids.indexOf(data.wccf_upload_progress_id), 1); 979 } 980 981 // Handle error 982 if (typeof data.result !== 'object' || typeof data.result.result === 'undefined' || data.result.result !== 'success') { 983 handle_file_upload_error(data, field_container); 984 return; 985 } 986 987 // Add file size to array 988 var current_file_sizes = field_container.data('wccf_uploaded_file_sizes'); 989 current_file_sizes[data.result.access_key] = data.result.file_size; 990 field_container.data('wccf_uploaded_file_sizes', current_file_sizes); 991 992 // Display uploaded file 993 field_container.find('.wccf_file_upload_status_' + data.wccf_upload_progress_id).animate({'opacity': 0}, 500, function() { 994
995 // Add file list container if needed 996 if (field_container.find('.wccf_file_upload_list').length === 0) { 997 998 // Special handling for user fields in backend user edit page 999 if (field_container.hasClass('wccf_user_profile_field_container')) { 1000 field_container.find('.wccf_user_field_file').before('<div class="wccf_file_upload_list"></div>'); 1001 } 1002 else { 1003 field_container.find('label[for="' + field_id + '"]').after('<div class="wccf_file_upload_list"></div>'); 1004 } 1005 } 1006 1007 // Add file to list 1008 field_container.find('.wccf_file_upload_list').append('<small class="wccf_file_upload_item">' + data.files[0].name + ' <span class="wccf_file_upload_delete">' + wccf_general_config.messages.file_upload_delete + '</span></small>'); 1009 1010 // Maybe add left border class 1011 toggle_file_upload_list_border(field_container); 1012 1013 // Select last element in list 1014 field_container.find('.wccf_file_upload_list .wccf_file_upload_item').last().each(function() { 1015 1016 // Inject hidden field that will submit file access keys along with regular form submit 1017 jQuery(this).append('<input type="hidden" class="' + hidden_field_class + '" name="' + hidden_field_name + '" value="' + data.result.access_key + '" data-wccf-file-access-key="' + data.result.access_key + '">'); 1018 1019 // Handle file removal 1020 file_removal_setup(jQuery(this), field_container, false); 1021 }); 1022 1023 // Remove upload status message 1024 jQuery(this).remove(); 1025 1026 // Trigger live product update 1027 form.trigger('rightpress_live_product_update_trigger'); 1028 1029 // Refresh checkout view if this is a checkout field with pricing 1030 refresh_checkout_on_checkout_file_field_change(field); 1031 1032 // Toggle required property for file field in backend 1033 toggle_file_required(field_container.find('input[data-wccf-field-id]').first()); 1034 1035 // Recheck frontend conditions 1036 wccf_check_frontend_conditions(field_container.closest('form')); 1037 }); 1038 }, 1039 fail: function (e, data) { 1040 1041 // Remove from tracked progress ids array 1042 if (wccf_upload_progress_ids.indexOf(data.wccf_upload_progress_id) !== -1) { 1043 wccf_upload_progress_ids.splice(wccf_upload_progress_ids.indexOf(data.wccf_upload_progress_id), 1); 1044 } 1045 1046 // Handle error 1047 handle_file_upload_error(data, field_container); 1048 1049 // Toggle required property for file field in backend 1050 toggle_file_required(field_container.find('input[data-wccf-field-id]').first()); 1051 1052 // Recheck frontend conditions 1053 wccf_check_frontend_conditions(field_container.closest('form')); 1054 }, 1055 send: function (e, data) { 1056 data.data.append('wccf_uploaded_file_sizes', JSON.stringify(field_container.data('wccf_uploaded_file_sizes'))); 1057 } 1058 }); 1059 1060 // Disable form submit if there's at least one file uploading 1061 form.submit(function(e) { 1062 if (wccf_upload_progress_ids.length) { 1063 e.preventDefault(); 1064 } 1065 }); 1066 1067 // Set up file removal for any existing files 1068 field_container.find('.wccf_file_upload_list .wccf_file_upload_item').each(function() { 1069 file_removal_setup(jQuery(this), field_container, preserve_existing_files_on_server); 1070 }); 1071 } 1072 jQuery('form .wccf_file').each(function() { 1073 wccf_set_up_file_upload_handler_for_field(jQuery(this), false); 1074 }); 1075 1076 /** 1077 * Handle file upload error 1078 */ 1079 function handle_file_upload_error(data, field_container) 1080 { 1081 // Select correct status element 1082 var status_element = field_container.find('.wccf_file_upload_status_' + data.wccf_upload_progress_id); 1083 1084 // Handle abort 1085 if (typeof data === 'object' && typeof data.errorThrown !== 'undefined' && data.errorThrown === 'abort') { 1086 status_element.remove(); 1087 } 1088 1089 // Get error message 1090 if (typeof data === 'object' && typeof data.result === 'object' && typeof data.result.error_message !== 'undefined') { 1091 var error_message = data.result.error_message; 1092 } 1093 else if (typeof data === 'object' && typeof data.files === 'object' && typeof data.files[0] === 'object' && typeof data.files[0].name !== 'undefined') { 1094 var error_message = wccf_general_config.messages.file_upload_error + ' ' + data.files[0].name; 1095 } 1096 else { 1097 var error_message = wccf_general_config.messages.file_upload_error; 1098 } 1099 1100 // Remove progess message 1101 status_element.find('.wccf_file_upload_message').remove(); 1102 1103 // Add error message
1104 status_element.prepend('<small class="wccf_file_upload_error">' + error_message + '</small>'); 1105 1106 // Remove upload status div with animation 1107 status_element.find('.wccf_file_upload_progress_wrapper').css('background-color', '#e44b23'); 1108 status_element.find('.wccf_file_upload_progress').animate({'width': 0}, 500, function() { 1109 status_element.delay(2500).animate({'opacity': 0}).animate({'width': 0, 'height': 0}, 500, function() { 1110 jQuery(this).remove(); 1111 }); 1112 }); 1113 } 1114 1115 /** 1116 * Toggle file upload list border 1117 * Displayed when multiple items are present 1118 */ 1119 function toggle_file_upload_list_border(field_container) 1120 { 1121 var list = field_container.find('.wccf_file_upload_list'); 1122 1123 if (list.find('.wccf_file_upload_item').length > 1) { 1124 if (!list.hasClass('wccf_file_upload_left_border')) { 1125 list.addClass('wccf_file_upload_left_border'); 1126 } 1127 } 1128 else { 1129 list.removeClass('wccf_file_upload_left_border'); 1130 } 1131 } 1132 1133 /** 1134 * Set up file removal 1135 */ 1136 function file_removal_setup(file_upload_item, field_container, preserve_existing_files_on_server) 1137 { 1138 1139 file_upload_item.find('.wccf_file_upload_delete').click(function() { 1140 1141 // Reference hidden field 1142 var hidden_field = file_upload_item.closest('.wccf_file_upload_item').find('input[data-wccf-file-access-key]'); 1143 1144 if (hidden_field) { 1145 1146 // Get access key and field id 1147 var access_key = hidden_field.val(); 1148 var field_id = hidden_field.closest('.wccf_file_upload_list').parent().find('input[data-wccf-field-id]').data('wccf-field-id'); 1149 1150 // Check if file can be removed from server 1151 if (!preserve_existing_files_on_server) { 1152 1153 // Send request to remove from server 1154 jQuery.post( 1155 wccf_general_config.ajaxurl, 1156 { 1157 action: 'wccf_remove_file', 1158 access_key: access_key, 1159 field_id: field_id 1160 } 1161 ); 1162 } 1163 1164 // Remove from file sizes list 1165 var current_file_sizes = field_container.data('wccf_uploaded_file_sizes'); 1166 1167 if (typeof current_file_sizes[access_key] !== 'undefined') { 1168 delete current_file_sizes[access_key]; 1169 field_container.data('wccf_uploaded_file_sizes', current_file_sizes); 1170 } 1171 } 1172 1173 // Remove from page 1174 file_upload_item.closest('.wccf_file_upload_item').animate({'opacity': 0}, 500, function() { 1175 1176 // Trigger live product update 1177 file_upload_item.closest('form').trigger('rightpress_live_product_update_trigger'); 1178 1179 // Remove file 1180 file_upload_item.remove(); 1181 1182 // Maybe remove left border class 1183 toggle_file_upload_list_border(field_container); 1184 1185 // Toggle required property for file field 1186 toggle_file_required(field_container.find('input[data-wccf-field-id]').first()); 1187 1188 // Recheck frontend conditions 1189 wccf_check_frontend_conditions(field_container.closest('form')); 1190 1191 // Refresh checkout view if this is a checkout field with pricing 1192 refresh_checkout_on_checkout_file_field_change(field_container.find('input.wccf_file')); 1193 }); 1194 }); 1195 } 1196 1197 /** 1198 * Set up existing file removal 1199 */ 1200 jQuery('.wccf_meta_box_field_container, .wccf_user_profile_field_container, .wccf_field_container_user_field, .wccf_field_container_file').each(function() { 1201 1202 var field_container = jQuery(this); 1203 1204 field_container.find('.wccf_file_upload_list .wccf_file_upload_item').each(function() { 1205 file_removal_setup(jQuery(this), field_container, false); 1206 }); 1207 }); 1208 1209 /** 1210 * Refresh checkout view if this is a checkout field with pricing 1211 */ 1212 function refresh_checkout_on_checkout_file_field_change(file_upload_field) 1213 { 1214 if (file_upload_field.hasClass('wccf_checkout_field') && file_upload_field.data('wccf-checkout-pricing')) { 1215 jQuery('body').trigger('update_checkout'); 1216 } 1217 } 1218 /** 1219 * Checkout field view update 1220 */ 1221 jQuery(document.body).on('updated_checkout', function() { 1222 1223 // Take reference of the form 1224 var form = jQuery('form.checkout'); 1225 1226 // Get list of field ids displayed in page 1227 var field_ids = []; 1228 1229 jQuery('.wccf_checkout_field[data-wccf-field-id]').each(function() { 1230 field_ids.push(jQuery(this).data('wccf-field-id')); 1231 }); 1232 1233 // Send request 1234 jQuery.ajax({ 1235 type: 'POST', 1236 url: wccf_general_config.ajaxurl, 1237 data: { 1238 action: 'wccf_update_checkout_field_view', 1239 field_ids: field_ids 1240 }, 1241 dataType: 'json', 1242 dataFilter: jQuery.rightpress.sanitize_json_response, 1243 success: function (response) { 1244 1245 // Check if request was successful 1246 if (typeof response === 'object' && response !== null && typeof response.result !== 'undefined' && response.result === 'success') { 1247 1248 // Check if there are fields to remove 1249 if (typeof response.remove === 'object') { 1250 1251 // Iterate over fields to remove 1252 for (var field_id in response.remove) { 1253 if (response.remove.hasOwnProperty(field_id)) { 1254 1255 // Remove field 1256 jQuery('.wccf_checkout_field[data-wccf-field-id="' + field_id + '"]').closest('.wccf_field_container').fadeOut(function() { 1257 remove_checkout_field(jQuery(this)); 1258 }); 1259 } 1260 } 1261 } 1262 1263 // Check if there are fields to add 1264 if (typeof response.add === 'object') { 1265 1266 // Iterate over positions 1267 for (var position in response.add) { 1268 if (response.add.hasOwnProperty(position)) { 1269 1270 // Get position properties 1271 var position_method = response.add[position]['position']['method']; 1272 var position_selector = response.add[position]['position']['selector']; 1273 1274 // Get fields to add to current position 1275 var fields = response.add[position]['fields']; 1276 1277 // Maybe reverse order of fields 1278 if (position_method === 'after' || position_method === 'append') { 1279 fields.reverse(); 1280 } 1281 1282 // Iterate over fields of current position 1283 jQuery.each(fields, function(index, field) { 1284 1285 // Insert field html into DOM 1286 jQuery(position_selector)[position_method](field.html); 1287 1288 // Reference new field 1289 var new_field = jQuery('.wccf_checkout_field[data-wccf-field-id="' + field.id + '"]').closest('.wccf_field_container'); 1290 1291 // Animate field 1292 new_field.hide().fadeIn(); 1293 1294 // Set up field functionality after dynamic field view update 1295 set_up_field_functionality_after_view_update(new_field, form, false); 1296 }); 1297 } 1298 } 1299 1300 // Recheck frontend conditions 1301 wccf_check_frontend_conditions(form); 1302 } 1303 } 1304 } 1305 }); 1306 }); 1307 1308 /** 1309 * Remove checkout field 1310 */ 1311 function remove_checkout_field(field) 1312 { 1313
1314 // Remove color picker mouseup event listener from document 1315 if (field.find('.wccf_checkout_field_color').length) { 1316 unbind_color_picker_toggle(field.find('.wccf_checkout_field_color').last()); 1317 } 1318 1319 // Remove field 1320 field.remove(); 1321 } 1322 1323 /** 1324 * Trigger checkout update on payment method change 1325 */ 1326 function set_up_payment_method_checkout_update() 1327 { 1328 jQuery('form.checkout input[name="payment_method"]').each(function() { 1329 if (typeof jQuery(this).data('wccf_payment_method_checkout_update') === 'undefined') { 1330 jQuery(this).change(function() { 1331 jQuery('body').trigger('update_checkout'); 1332 }); 1333 jQuery(this).data('wccf_payment_method_checkout_update', true); 1334 } 1335 }); 1336 } 1337 jQuery('body').on('updated_checkout', set_up_payment_method_checkout_update); 1338 set_up_payment_method_checkout_update(); 1339 1340 /** 1341 * Backend field editing 1342 */ 1343 jQuery('span.wccf_backend_editing_value[data-wccf-backend-editing="1"]').each(function() { 1344 1345 // Bind click event 1346 jQuery(this).click(function() { 1347 1348 var value_element = jQuery(this); 1349 1350 // Get field element via ajax 1351 jQuery.post( 1352 wccf_general_config.ajaxurl, 1353 { 1354 action: 'wccf_get_backend_editing_field', 1355 field_id: value_element.data('wccf-field-id'), 1356 item_id: value_element.data('wccf-item-id'), 1357 quantity_index: value_element.data('wccf-quantity-index') 1358 }, 1359 function(response) { 1360 1361 // Parse response 1362 response = parse_ajax_json_response(response); 1363 1364 // Check for errors 1365 if (typeof response === 'object' && typeof response.result !== 'undefined' && response.result === 'success' && typeof response.field !== 'undefined') { 1366 1367 // Display field and hide value 1368 value_element.after(response.field).hide(); 1369 1370 // Date/time picker setup 1371 jQuery.each(['date', 'time', 'datetime'], function(index, type) { 1372 value_element.parent().find('.wccf_backend_editing_' + type).each(function() { 1373 set_up_datetimepicker(jQuery(this), type); 1374 }); 1375 }); 1376 1377 // Color picker setup 1378 value_element.parent().find('.wccf_backend_editing_color').each(function() { 1379 set_up_color_picker(jQuery(this)); 1380 }); 1381 } 1382 } 1383 ); 1384 }); 1385 }); 1386 1387 /** 1388 * Enable form validation on user edit page 1389 */ 1390 jQuery('form#your-profile, form#createuser, form#adduser').filter(':has(.wccf)').removeAttr('novalidate'); 1391 1392 /** 1393 * Parse Ajax JSON response 1394 */ 1395 function parse_ajax_json_response(response, return_raw_data) 1396 { 1397 // Check if we need to return parsed object or potentially fixed raw data 1398 var return_raw_data = (typeof return_raw_data !== 'undefined') ? return_raw_data : false; 1399 1400 try { 1401 1402 // Attempt to parse data 1403 var parsed = jQuery.parseJSON(response); 1404 1405 // Return appropriate value 1406 return return_raw_data ? response : parsed; 1407 } 1408 catch (e) { 1409 1410 // Attempt to fix malformed JSON string 1411 var valid_response = response.match(/{"result.*"}/); 1412 1413 // Check if we were able to fix it 1414 if (valid_response !== null) { 1415 response = valid_response[0]; 1416 } 1417 } 1418 1419 // Second attempt to parse response data 1420 return return_raw_data ? response : jQuery.parseJSON(response); 1421 } 1422 1423 /** 1424 * Fix HTML5 required validation for file uploads 1425 */ 1426 function toggle_file_required(file_field) 1427 { 1428 // Get uploaded files 1429 var upload_list = file_field.closest('.wccf_field_container, .wccf_meta_box_field_container, .wccf_user_profile_field_container').find('.wccf_file_upload_list').children(); 1430 1431 // Remove required property 1432 if (upload_list.length > 0 && file_field.prop('required')) { 1433 file_field.prop('required', false); 1434 file_field.data('wccf-file-field-was-required', 1); 1435 } 1436 // Add required property back 1437 else if (upload_list.length == 0 && file_field.data('wccf-file-field-was-required')) { 1438 file_field.prop('required', true); 1439 file_field.data('wccf-file-field-was-required', 0); 1440 } 1441 } 1442 jQuery('.wccf_field_container, .wccf_meta_box_field_container, .wccf_user_profile_field_container').find('.wccf_file[required]').each(function() { 1443 toggle_file_required(jQuery(this)); 1444 }); 1445 1446 /** 1447 * Fix HTML5 required validation for checkbox set 1448 */ 1449 jQuery('.wccf_field_container, .wccf_meta_box_field_container, .wccf_user_profile_field_container').has('.wccf_checkbox').each(function() { 1450 1451 // Reference all checkboxes 1452 var checkboxes = jQuery(this).find('.wccf_checkbox'); 1453 1454 // Remove required attribute from all checkboxes when at least one is checked 1455 if (checkboxes.length > 1 && checkboxes.prop('required')) { 1456 1457 function toggle_checkboxes_required() 1458 { 1459 // Get min selected value 1460 var min_selected = checkboxes.data('wccf-min-selected'); 1461 min_selected = typeof min_selected !== 'undefined' ? min_selected : 1; 1462 1463 // Check if enough checkboxes were selected 1464 if (checkboxes.filter(':checked').length >= min_selected) { 1465 checkboxes.prop('required', false); 1466 } 1467 else { 1468 checkboxes.prop('required', true); 1469 } 1470 } 1471 checkboxes.change(function() { 1472 toggle_checkboxes_required(); 1473 }); 1474 toggle_checkboxes_required(); 1475 } 1476 }); 1477 1478 /** 1479 * Fix HTML5 min selected validation for multiselect fields 1480 */ 1481 jQuery('.wccf_multiselect[data-wccf-min-selected]').each(function() { 1482 1483 // Reference field 1484 var multiselect = jQuery(this); 1485 1486 // Get min selected value 1487 var min_selected = multiselect.data('wccf-min-selected'); 1488 1489 // Handle form submit 1490 multiselect.closest('form').submit(function(e) { 1491 1492 // More options need to be selected 1493 if (multiselect.find('option:selected').length < min_selected) { 1494 jQuery(this).find('option:selected').prop('selected', false); 1495 e.preventDefault(); 1496 } 1497 }); 1498 }); 1499 1500 /** 1501 * Set up color picker 1502 */ 1503 function set_up_color_picker(input) 1504 { 1505 1506 // Get current input value 1507 var value = input.val(); 1508 1509 // Get color to show 1510 var color = (typeof value !== 'undefined' && value !== '') ? value : '#000000'; 1511 1512 // Set color on input element 1513 input.css('border-left', ('29px solid ' + color)); 1514 1515 // Change callback 1516 wccf_color_picker_config.change = function(event, ui) { 1517 1518 // Update value 1519 input.val(ui.color.toString()); 1520 1521 // Update color 1522 input.css('border-left', ('29px solid ' + ui.color.toString())); 1523 1524 // Trigger change event 1525 input.change(); 1526 }; 1527 1528 // Initialize Iris color picker 1529 input.iris(wccf_color_picker_config); 1530 1531 // Fix position 1532 input.closest('.wccf_field_container').find('.iris-picker').css('position', 'absolute').css('z-index', '999'); 1533 1534 // Bind color picker toggle event 1535 bind_color_picker_toggle(input); 1536 } 1537 1538 /** 1539 * Bind color picker toggle event 1540 */ 1541 function bind_color_picker_toggle(input) 1542 { 1543 1544 // Format dynamic function name 1545 var function_name = 'toggle_color_picker_' + input.prop('id'); 1546 1547 // Make sure function does not exist yet 1548 if (typeof checkout_color_picker_handlers[function_name] === 'undefined') { 1549 1550 // Add function with dynamic reference 1551 checkout_color_picker_handlers[function_name] = function(event) { 1552 toggle_color_picker(event); 1553 }; 1554 1555 // Bind mouseup event 1556 jQuery(document).on('mouseup', '*', {input: input}, checkout_color_picker_handlers[function_name]); 1557 } 1558 } 1559 1560 /** 1561 * Unbind color picker toggle event 1562 */ 1563 function unbind_color_picker_toggle(input) 1564 { 1565 1566 // Format dynamic function name 1567 var function_name = 'toggle_color_picker_' + input.prop('id'); 1568 1569 // Check if function is defined 1570 if (typeof checkout_color_picker_handlers[function_name] !== 'undefined') { 1571 1572 // Unbind mouseup event 1573 jQuery(document).off('mouseup', '*', checkout_color_picker_handlers[function_name]); 1574 1575 // Remove function 1576 delete checkout_color_picker_handlers[function_name]; 1577 } 1578 } 1579 1580 /** 1581 * Color picker state toggling 1582 */ 1583 function toggle_color_picker(event) 1584 { 1585 1586 var container = jQuery(event.data.input).closest('.wccf_field_container'); 1587 1588 // Clicked on color input 1589 if (jQuery(event.target).is(event.data.input)) { 1590 event.data.input.iris('show'); 1591 } 1592 // Clicked outside input and container 1593 else if (!container.is(event.target) && !container.has(event.target).length) { 1594 event.data.input.iris('hide'); 1595 } 1596 } 1597 1598 /** 1599 * Set up datetime picker 1600 */ 1601 function set_up_datetimepicker(element, type) 1602 { 1603 1604 // Select config 1605 if (type === 'date') { 1606 var config = wccf_datetimepicker_date_config.x; 1607 } 1608 else if (type === 'time') { 1609 var config = wccf_datetimepicker_time_config.x; 1610 } 1611 else { 1612 var config = wccf_datetimepicker_datetime_config.x; 1613 } 1614 1615 // Set z-index 1616 if (typeof config.style !== 'undefined') { 1617 config.style += ' z-index: 999999999;';
1618 } 1619 else { 1620 config.style = 'z-index: 999999999'; 1621 } 1622 1623 // Initialize datepicker 1624 element.datetimepicker(config); 1625 1626 // Set locale 1627 jQuery.datetimepicker.setLocale(wccf_datetimepicker_locale.x); 1628 } 1629 1630 /** 1631 * Checkout validation 1632 */ 1633 jQuery('form.checkout').submit(function() { 1634 unset_field_invalid_flags(jQuery(document.body)); 1635 }); 1636 1637 if (jQuery('form.checkout').length) { 1638 jQuery(document.body).on('checkout_error', function() { 1639 set_field_invalid_flags(jQuery(document.body)); 1640 }); 1641 } 1642 1643 /** 1644 * Set field invalid flags 1645 * 1646 * Note: This is used for both checkout field validation and cart item product field editing validation 1647 */ 1648 function set_field_invalid_flags(parent) 1649 { 1650 1651 // Find active error messages 1652 parent.find('[data-wccf-error-field-id]').each(function() { 1653 1654 // Get field id 1655 var field_id = jQuery(this).data('wccf-error-field-id'); 1656 1657 // Select field by id 1658 parent.find('.wccf[data-wccf-field-id="' + field_id + '"]').each(function() { 1659 1660 // Reference input and container 1661 var input = jQuery(this); 1662 var container = input.closest('.wccf_field_container'); 1663 1664 // Add invalid class to container 1665 container.addClass('wccf_field_invalid'); 1666 1667 // Remove invalid class on input focus or change 1668 input.on('focus change keyup', function() { 1669 container.removeClass('wccf_field_invalid'); 1670 }); 1671 }); 1672 }); 1673 } 1674 1675 /** 1676 * Unset field invalid flags 1677 * 1678 * Note: This is used for both checkout field validation and cart item product field editing validation 1679 */ 1680 function unset_field_invalid_flags(parent) 1681 { 1682 parent.find('.wccf_field_invalid').removeClass('wccf_field_invalid'); 1683 } 1684 1685 /** 1686 * Simple product field frontend validation 1687 */ 1688 jQuery('#wccf_product_field_master_container').closest('form').submit(function(e) { 1689 1690 var focused = false; 1691 1692 // Clear all error states 1693 jQuery(this).find('.wccf_field_invalid').removeClass('wccf_field_invalid'); 1694 1695 // Select all required visible non-disabled product field inputs 1696 jQuery(this).find('.wccf_product_field[data-wccf-required-field]:not(:disabled):visible').each(function() { 1697 1698 // Reference input and container 1699 var input = jQuery(this); 1700 var container = input.closest('.wccf_field_container'); 1701 1702 // Check if field has value 1703 if (input.prop('type') === 'file') { 1704 var has_value = !!container.find('.wccf_file_upload_item').length; 1705 } 1706 else if (input.prop('type') === 'checkbox' || input.prop('type') === 'radio') { 1707 var has_value = !!container.find('.wccf_product_field:checked').length; 1708 } 1709 else { 1710 var has_value = !!jQuery(this).val(); 1711 } 1712 1713 // Check if field is empty 1714 if (!has_value) { 1715 1716 // Focus on first input with error 1717 if (!focused) { 1718 input.focus(); 1719 focused = true; 1720 } 1721 1722 // Add invalid class 1723 container.addClass('wccf_field_invalid'); 1724 1725 // Print error message 1726 input.after('<div class="wccf_required_field_error">' + wccf_general_config.messages.required_field + '</div>'); 1727 1728 // Clean up on focus or value change 1729 input.on('focus change keyup', function() { 1730 1731 // Remove invalid class 1732 container.removeClass('wccf_field_invalid'); 1733 1734 // Remove error message 1735 container.find('.wccf_required_field_error').remove(); 1736 }); 1737 1738 // Do not submit form 1739 e.preventDefault(); 1740 } 1741 }); 1742 }); 1743 1744 /** 1745 * Cart item product field editing 1746 */ 1747 if (jQuery('#wccf_cart_item_product_field_editing_modal').length) { 1748 1749 // Reference modal 1750 var modal = jQuery('#wccf_cart_item_product_field_editing_modal'); 1751 var wrapper = modal.find('.wccf_modal_wrapper'); 1752 var content = modal.find('.wccf_modal_content'); 1753 var close = modal.find('.wccf_modal_close'); 1754 var form = null; 1755 1756 // Define preloader 1757 var preloader = '<div class="wccf_modal_preloader"></div>'; 1758 1759 // Open modal on click 1760 jQuery('form.woocommerce-cart-form .wccf_cart_item_product_field_editing').click(function(e) { 1761 1762 // Get field id and cart item key 1763 var field_id = jQuery(this).data('wccf-cart-item-edit-field-id'); 1764 var cart_item_key = jQuery(this).data('wccf-cart-item-key'); 1765 1766 // Open modal 1767 cart_item_product_field_editing_open(field_id, cart_item_key); 1768 1769 // Prevent file download 1770 e.preventDefault(); 1771 }); 1772 1773 // Close on close target click 1774 close.on('click', cart_item_product_field_editing_close); 1775 1776 function cart_item_product_field_editing_open(field_id, cart_item_key) 1777 { 1778 1779 // Make field ids array 1780 // Note: currently we support single field editing but this can be changed to support multiple fields 1781 var field_ids = [field_id]; 1782 1783 // Add preloader 1784 content.html(preloader); 1785 1786 // Load fields 1787 load_fields(field_ids, cart_item_key); 1788 1789 // Fade in modal 1790 modal.css('opacity', 0).css('display', 'table').animate({opacity: 1}, function() { 1791 1792 // Close on click outside of modal 1793 jQuery(document).on('mousedown', cart_item_product_field_editing_close_callback); 1794 }); 1795 } 1796 1797 function cart_item_product_field_editing_close_callback(event) 1798 { 1799 1800 if (!wrapper.is(event.target) && !wrapper.has(event.target).length) { 1801 cart_item_product_field_editing_close(); 1802 } 1803 } 1804 1805 function cart_item_product_field_editing_close() 1806 { 1807 1808 // Fade out modal 1809 modal.animate({opacity: 0}, function() { 1810 1811 // Hide modal 1812 modal.css('display', 'none'); 1813 1814 // Clear content 1815 content.html(''); 1816 1817 // Unset form 1818 form = null; 1819 1820 // Remove click outside of modal callback 1821 jQuery(document).off('mousedown', cart_item_product_field_editing_close_callback); 1822 }); 1823 } 1824 1825 function load_fields(field_ids, cart_item_key) 1826 { 1827 1828 // Send request 1829 jQuery.ajax({ 1830 type: 'POST', 1831 url: wccf_general_config.ajaxurl, 1832 data: { 1833 action: 'wccf_get_cart_item_product_field_editing_view', 1834 field_ids: field_ids, 1835 cart_item_key: cart_item_key, 1836 }, 1837 dataType: 'json', 1838 dataFilter: jQuery.rightpress.sanitize_json_response, 1839 success: function (response) { 1840 1841 // Check if request was successful 1842 if (typeof response === 'object' && response !== null && typeof response.result !== 'undefined' && response.result === 'success') { 1843 1844 // Set fields html 1845 content.html(response.html); 1846 1847 // Set form 1848 form = content.find('form#wccf_cart_item_product_field_editing_form'); 1849 1850 // Set up advanced field functionality on all inputs 1851 form.find('.wccf_field_container').each(function() { 1852 set_up_field_functionality_after_view_update(jQuery(this), form, true); 1853 }); 1854 1855 // Reference first input 1856 var first_input = form.find('.wccf_product_field').first(); 1857 1858 // Focus on first element and move typing cursor to the back for some file types 1859 if ((first_input.is('input') && ['text', 'password', 'email', 'number'].indexOf(first_input.prop('type')) !== -1) || first_input.is('textarea')) { 1860 var field_value = first_input.val(); 1861 first_input.focus().val('').val(field_value); 1862 } 1863 1864 // Handle submit event 1865 form.submit(function(e) { 1866 1867 // Handle submit 1868 update_cart_item_field_values(); 1869
1870 // Do not navigate away from page 1871 e.preventDefault(); 1872 }); 1873 } 1874 // Unable to get fields view 1875 else { 1876 1877 handle_load_fields_error(); 1878 } 1879 }, 1880 error: function () { 1881 1882 handle_load_fields_error(); 1883 } 1884 }); 1885 1886 function update_cart_item_field_values() 1887 { 1888 1889 // Unset current error messages 1890 form.find('.wccf_modal_validation_error').fadeOut(function() { jQuery(this).remove(); }); 1891 unset_field_invalid_flags(form); 1892 1893 // Prepare data 1894 var form_data = form.serialize(); 1895 1896 // Send request 1897 jQuery.ajax({ 1898 type: 'POST', 1899 url: wccf_general_config.ajaxurl, 1900 data: { 1901 action: 'wccf_update_cart_item_product_field_values', 1902 data: form_data, 1903 }, 1904 dataType: 'json', 1905 dataFilter: jQuery.rightpress.sanitize_json_response, 1906 success: function (response) { 1907 1908 // Request processed successfully 1909 if (typeof response === 'object' && response !== null && typeof response.result !== 'undefined' && response.result === 'success') { 1910 1911 // Show success message 1912 content.html('<div class="wccf_modal_success">' + wccf_general_config.messages.field_values_updated + '<br>' + wccf_general_config.messages.page_reload + '</div>'); 1913 1914 // Reload page after a pause 1915 setTimeout(function(){ 1916 window.location.reload(true); 1917 }, 1500); 1918 } 1919 // Field validation error 1920 else if (typeof response === 'object' && response !== null && typeof response.result !== 'undefined' && response.result === 'error' && typeof response.validation_error_messages === 'object' && response.validation_error_messages !== null && response.validation_error_messages.length) { 1921 1922 // Handle validation errors 1923 handle_validation_errors(response.validation_error_messages); 1924 } 1925 // Unknown error occurred while processing request 1926 else { 1927 1928 handle_update_cart_item_field_values_error(); 1929 } 1930 }, 1931 error: function () { 1932 1933 handle_update_cart_item_field_values_error(); 1934 } 1935 }); 1936 } 1937 1938 function handle_load_fields_error() 1939 { 1940 1941 // Display error message 1942 content.html('<div class="wccf_modal_error">' + wccf_general_config.messages.error_loading_field_view + '<br>' + wccf_general_config.messages.try_again + '</div>'); 1943 1944 // Close modal after a pause 1945 setTimeout(function(){ 1946 cart_item_product_field_editing_close(); 1947 }, 2500); 1948 } 1949 1950 function handle_update_cart_item_field_values_error() 1951 { 1952 1953 // Display error message 1954 content.html('<div class="wccf_modal_error">' + wccf_general_config.messages.error_updating_field_values + '<br>' + wccf_general_config.messages.try_again + '</div>'); 1955 1956 // Close modal after a pause 1957 setTimeout(function(){ 1958 cart_item_product_field_editing_close(); 1959 }, 2500); 1960 } 1961
1962 function handle_validation_errors(messages) 1963 { 1964 1965 // Iterate over validation error messages 1966 for (var message_index in messages) { 1967 if (messages.hasOwnProperty(message_index)) { 1968 1969 // Add validation error 1970 add_validation_error(messages[message_index]); 1971 } 1972 } 1973 1974 // Show errors 1975 form.find('.wccf_modal_validation_error').fadeIn(); 1976 1977 // Set invalid flags 1978 set_field_invalid_flags(form); 1979 } 1980 1981 function add_validation_error(message) 1982 { 1983 1984 form.prepend('<div class="wccf_modal_validation_error">' + message + '</div>'); 1985 } 1986 } 1987 } 1988 1989 1990 1991 1992 1993 1994 1995});
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.