Current File : /home/itiffy/public_html/blog/wp-content/plugins/wp-invoice/static/scripts/src/wpi-functions.js
/**
 * Toggle advanced options that are somehow related to the clicked trigger
 *
 * If trigger element has an attr of 'show_type_source', then function attempt to find that element and get its value
 * if value is found, that value is used as an additional requirement when finding which elements to toggle
 *
 * Example: <span class="wpi_show_advanced" show_type_source="id_of_input_with_a_string" advanced_option_class="class_of_elements_to_trigger" show_type_element_attribute="attribute_name_to_match">Show Advanced</span>
 * The above, when clicked, will toggle all elements within the same parent tree of cicked element, with class of "advanced_option_class" and with attribute of "show_type_element_attribute" the equals value of "#id_of_input_with_a_string"
 *
 * Clicking the trigger in example when get the value of:
 * <input id="value_from_source_element" value="some_sort_of_identifier" />
 *
 * And then toggle all elements like below:
 * <li class="class_of_elements_to_trigger" attribute_name_to_match="some_sort_of_identifier">Data that will be toggled.</li>
 *
 * Copyright 2011 Usability Dynamics, Inc. <info@usabilitydynamics.com>
 */
function wpi_toggle_advanced_options ( this_element ) {

  var advanced_option_class = false;
  var show_type = false;
  var show_type_element_attribute = false;

  //** Try getting arguments automatically */
  var wrapper = (jQuery( this_element ).attr( 'wrapper' ) ? jQuery( this_element ).closest( '.' + jQuery( this_element ).attr( 'wrapper' ) ) : jQuery( this_element ).parents( '.wpi_dynamic_table_row' ));

  if ( jQuery( this_element ).attr( "advanced_option_class" ) !== undefined ) {
    var advanced_option_class = "." + jQuery( this_element ).attr( "advanced_option_class" );
  }

  if ( jQuery( this_element ).attr( "show_type_element_attribute" ) !== undefined ) {
    var show_type_element_attribute = jQuery( this_element ).attr( "show_type_element_attribute" );
  }

  //** If no advanced_option_class is found in attribute, we default to 'wpi_advanced_option' */
  if ( !advanced_option_class ) {
    advanced_option_class = ".wpi_advanced_option";
  }

  //** If element does not have a table row wrapper, we look for the closts .wpi_something_advanced_wrapper wrapper */
  if ( wrapper.length == 0 ) {
    var wrapper = jQuery( this_element ).parents( '.wpi_something_advanced_wrapper' );
  }

  //** get_show_type_value forces the a look up a value of a passed element, ID of which is passed, which is then used as another conditional argument */
  if ( show_type_source = jQuery( this_element ).attr( "show_type_source" ) ) {
    var source_element = jQuery( "#" + show_type_source );

    if ( source_element ) {
      //** Element found, determine type and get current value */
      if ( jQuery( source_element ).is( "select" ) ) {
        show_type = jQuery( "option:selected", source_element ).val();
      }
    }
  }

  if ( !show_type ) {
    element_path = jQuery( advanced_option_class, wrapper );
  }

  //** Look for advanced options with show type */
  if ( show_type ) {
    element_path = jQuery( advanced_option_class + "[" + show_type_element_attribute + "='" + show_type + "']", wrapper );
  }

  //** Check if this_element element is a checkbox, we assume that we always show things when it is checked, and hiding when unchecked */
  if ( jQuery( this_element ).is( "input[type=checkbox]" ) ) {

    var toggle_logic = jQuery( this_element ).attr( "toggle_logic" );

    if ( jQuery( this_element ).is( ":checked" ) ) {
      if ( toggle_logic == 'reverse' ) {
        jQuery( element_path ).hide();
      } else {
        jQuery( element_path ).show();
      }
    } else {
      if ( toggle_logic == 'reverse' ) {
        jQuery( element_path ).show();
      } else {
        jQuery( element_path ).hide();
      }
    }

    return;

  } else if ( jQuery( this_element ).is( "select" ) ) {

    jQuery( advanced_option_class + "[" + show_type_element_attribute + "]", wrapper ).hide();

    if ( jQuery( this_element ).val() == show_type ) {
      jQuery( element_path ).show();
    } else {
      jQuery( element_path ).hide();
    }

    return;
  }

  jQuery( element_path ).toggle();

}

/*
 * Updates Row field names
 * @param object instance. DOM element
 * @param boolean allowRandomSlug. Determine if Row can contains random slugs.
 */
var wpi_updateRowNames = function ( instance, allowRandomSlug ) {
  if ( typeof instance == 'undefined' ) {
    return false;
  }
  if ( typeof allowRandomSlug == 'undefined' ) {
    var allowRandomSlug = false;
  }

  var this_row = jQuery( instance ).parents( 'tr.wpi_dynamic_table_row' );
  //** Slug of row in question */
  var old_slug = jQuery( this_row ).attr( 'slug' );
  //** Get data from input.slug_setter */
  var new_slug = jQuery( instance ).val();
  //** Convert into slug */
  var new_slug = wpi_create_slug( new_slug );

  //** Don't allow to blank out slugs */
  if ( new_slug == "" ) {
    if ( allowRandomSlug ) {
      new_slug = 'random_' + Math.floor( Math.random() * 1000 );
    } else {
      return;
    }
  }

  //** If slug input.slug exists in row, we modify it */
  jQuery( ".slug", this_row ).val( new_slug );
  //** Update row slug */
  jQuery( this_row ).attr( 'slug', new_slug );

  //** Cycle through all child elements and fix names */
  jQuery( 'input,select,textarea', this_row ).each( function ( element ) {
    var old_name = jQuery( this ).attr( 'name' );
    var new_name = old_name.replace( old_slug, new_slug );
    var old_id = jQuery( this ).attr( 'id' );
    var new_id = old_id.replace( old_slug, new_slug );
    //** Update to new name */
    jQuery( this ).attr( 'name', new_name );
    jQuery( this ).attr( 'id', new_id );
  } );

  //** Cycle through labels too */
  jQuery( 'label', this_row ).each( function ( element ) {
    var old_for = jQuery( this ).attr( 'for' );
    var new_for = old_for.replace( old_slug, new_slug );
    //** Update to new name */
    jQuery( this ).attr( 'for', new_for );
  } );
}

/**
 * Create slug
 */
function wpi_create_slug ( slug ) {

  slug = slug.replace( /[^a-zA-Z0-9_\s]/g, "" );
  slug = slug.toLowerCase();
  slug = slug.replace( /\s/g, '_' );

  return slug;
}

/**
 * Add row
 */
function wpi_add_row ( element ) {
  var auto_increment = false;
  var table = jQuery( element ).parents( '.ud_ui_dynamic_table' );
  var table_id = jQuery( table ).attr( "id" );

  //** Determine if table rows are numeric */
  if ( jQuery( table ).attr( 'auto_increment' ) == 'true' ) {
    var auto_increment = true;
  } else if ( jQuery( table ).attr( 'allow_random_slug' ) == 'true' ) {
    var allow_random_slug = true;
  }

  //** Clone last row */
  var cloned = jQuery( ".wpi_dynamic_table_row:last", table ).clone();

  //** Insert new row after last one */
  jQuery( cloned ).appendTo( table );

  //** Get Last row to update names to match slug */
  var added_row = jQuery( ".wpi_dynamic_table_row:last", table );

  //** Display row just in case */
  jQuery( added_row ).show();

  //** Blank out all values */
  jQuery( "textarea", added_row ).val( '' );
  jQuery( "input[type=text]", added_row ).val( '' );
  jQuery( "input[type=checkbox]", added_row ).attr( 'checked', false );
  jQuery( "textarea:disabled,input[type=text]:disabled,input[type=checkbox]:disabled", added_row ).removeAttr( 'disabled' );

  //** Increment name value automatically */
  if ( auto_increment ) {
    //** Cycle through all child elements and fix names */
    jQuery( 'input,select,textarea', added_row ).each( function ( element ) {
      var old_name = jQuery( this ).attr( 'name' );

      var matches = old_name.match( /\[(\d{1,2})\]/ );
      var old_count = false;
      var new_count = false;
      if ( matches ) {
        old_count = parseInt( matches[1] );
        new_count = (old_count + 1);
      }
      var new_name = old_name.replace( '[' + old_count + ']', '[' + new_count + ']' );

      //** Update to new name */
      jQuery( this ).attr( 'name', new_name );

    } );
  } else if ( allow_random_slug ) {
    //** Update Row names */
    var slug_setter = jQuery( "input.slug_setter", added_row );
    if ( slug_setter.length > 0 ) {
      wpi_updateRowNames( slug_setter.get( 0 ), true );
    }
  }

  //** Unset 'new_row' attribute */
  jQuery( added_row ).attr( 'new_row', 'true' );

  //** Focus on new element */
  jQuery( 'input.slug_setter', added_row ).focus();

  return added_row;

}

/**
 * Display error
 */
function wpi_show_error ( message ) {
  jQuery( '#wpi_single_error' ).remove();
  jQuery( "<div id='wpi_single_error' class='error fade below-h2'><p>" + message + "</p></div>" ).insertAfter( '.wrap > h2' );
}

/**
 * Displays success
 */
function wpi_show_success ( message ) {
  jQuery( '#message' ).remove();
  jQuery( "<div id='message' class='updated below-h2'><p>" + message + "</p></div>" ).insertAfter( '.wrap > h2' );
}

/**
 * Hide all errors
 */
function wpi_remove_errors ( message ) {
  jQuery( '#wpi_single_error' ).remove();
}

/**
 * Validates email address
 */
function wpi_validate_email ( address ) {
  var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,100})$/;
  if ( reg.test( address ) == false ) {
    return false;
  } else {
    return true;
  }
}

/**
 * Turn off quote. Usually happens when activating recurring.
 */
function wpi_disable_quote () {
  jQuery( '.wpi_wpi_invoice_quote_' ).attr( "checked", false );
}

/**
 * Turn on quote option.
 */
function wpi_enable_quote () {
  jQuery( '.wpi_quote_option' ).show();
  jQuery( '.wpi_wpi_invoice_quote_' ).attr( "checked", true );
}

/**
 * Fixes the payment/charge/adjustment dropdown to only allow for
 * accepting charges.  This way we don't overcomplicate things with recurring billing.
 */
function wpi_toggle_wpi_event_type () {
  if ( is_recurring ) {
    jQuery( "#wpi_event_type" ).val( 2 );
    jQuery( "#wpi_event_type" ).attr( "disabled", "disabled" );
  } else {
    jQuery( "#wpi_event_type" ).removeAttr( "disabled" );
  }
}

/**
 * Turns ON deposit options on invoice
 */
function wpi_enable_deposit () {
  //** Recurring invoice */
  wpi_disable_quote();
  wpi_disable_recurring();
  wpi_hide_recurring_option();
  wpi_hide_quote_option();
  jQuery( '.wpi_deposit_settings' ).show();
}
/**
 * TurnsOFF deposit options on invoice
 */
function wpi_disable_deposit () {
  wpi_show_recurring_option();
  wpi_show_quote_option();
  jQuery( '.wpi_deposit_settings' ).hide();
  jQuery( '.wpi_deposit_settings input' ).val( "" );
}
/*
 Hides deposit options on invoice
 */
function wpi_hide_deposit_option () {
  jQuery( '.wpi_hide_deposit_option' ).hide();
}
/*
 Show deposit options on invoice
 */
function wpi_show_deposit_option () {
  jQuery( '.wpi_hide_deposit_option' ).show();
}
/*
 Hides quote options on invoice
 */
function wpi_hide_quote_option () {
  jQuery( '.wpi_quote_option' ).hide();
}
/*
 Show quote options on invoice
 */
function wpi_show_quote_option () {
  jQuery( '.wpi_quote_option' ).show();
}
/*
 Turns ON recurring billing on invoice
 */
function wpi_enable_recurring () {
  // Recurring invoice
  wpi_disable_quote();
  wpi_disable_deposit();
  wpi_hide_deposit_option();

  /* Hide quote option */
  wpi_hide_quote_option();

  jQuery( '.wpi_recurring_options' ).show();
  jQuery( '.wpi_turn_off_recurring' ).show();
  jQuery( ".wpi_recurring_bill_settings" ).show();
  jQuery( ".wpi_not_for_recurring" ).hide();

  jQuery( document ).trigger('wpi_enable_recurring');

  is_recurring = true;
  wpi_toggle_wpi_event_type();
  wpi_recalc_totals();
}

/**
 * Toggle recurring start date
 */
function wpi_enable_recurring_start_date( type ) {
  jQuery( "#wpi_wpi_invoice_recurring_send_invoice_automatically_"+type ).removeAttr( "checked" );
  jQuery( ".wpi_recurring_start_date."+type ).show();
}
function wpi_disable_recurring_start_date( type ) {
  jQuery( "#wpi_wpi_invoice_recurring_send_invoice_automatically_"+type ).attr( "checked", "checked" );
  jQuery( ".wpi_recurring_start_date."+type ).hide();
}
/*
 Hide recurring option on invoice
 */
function wpi_hide_recurring_option () {
  jQuery( ".wpi_turn_off_recurring" ).hide();
}
/*
 Show recurring option on invoice
 */
function wpi_show_recurring_option () {
  jQuery( ".wpi_turn_off_recurring" ).show();
}
/*
 Turns off recurring billing on invoice
 */
function wpi_disable_recurring () {
  jQuery( '.wpi_invoice_adjustment_event_type_row' ).hide();
  jQuery( '.wpi_recurring_options' ).hide();
  jQuery( '.wpi_wpi_invoice_meta_recurring_active_' ).attr( 'checked', false );
  jQuery( '.wpi_turn_off_recurring input' ).attr( "checked", false );
  jQuery( '.wpi_recurring_bill_settings' ).val( '' );
  jQuery( '.wpi_recurring_bill_settings input' ).attr( 'checked', false );
  jQuery( '.wpi_recurring_bill_settings' ).hide();
  jQuery( ".wpi_not_for_recurring" ).show();
//    jQuery(".wpi_not_for_deposit").show();

  jQuery( document ).trigger('wpi_disable_recurring');

  is_recurring = false;
  wpi_recalc_totals();
  wpi_toggle_wpi_event_type();
}
/*
 Displays payment charge box
 */
function wpi_show_paycharge_box () {
  jQuery( '#postbox_status_and_history' ).show();
  jQuery( '#wpi_enter_payments' ).toggle();
  jQuery( '#wpi_enter_payments .input_field' ).val( '' );
  if ( jQuery( '#wpi_enter_payments' ).is( ":visible" ) ) {
    jQuery( '#wpi_enter_payments' ).parent().css( 'max-height', '286px' );
  } else {
    jQuery( '#wpi_enter_payments' ).parent().css( 'max-height', '150px' );
  }
  // Clear out fields
}
/*
 Process event from payment charge box
 */
function wpi_process_manual_event () {
  jQuery( '#wpi_process_manual_event' ).attr('disabled', 'disabled');

  var event_data;

  event_data = {
    action: "wpi_process_manual_event",
    nonce: jQuery( '#wpi_process_manual_event_nonce' ).val(),
    invoice_id: jQuery( '#wpi_invoice_id' ).val(),
    event_type: jQuery( '#wpi_event_type' ).val(),
    event_amount: jQuery( '#wpi_event_amount' ).val(),
    event_tax: jQuery( '#wpi_event_tax' ).val(),
    event_note: jQuery( '#wpi_event_note' ).val(),
    event_date: jQuery( '.wpi_event_date' ).val(),
    event_time: jQuery( '.wpi_event_time' ).val()
  };

  jQuery.ajax( {
    dataType: "json",
    data: event_data,
    beforeSend: function () {
    },
    type: "POST",
    url: ajaxurl,
    success: function ( data ) {

      jQuery( '#wpi_process_manual_event' ).removeAttr('disabled');

      if ( data.success == "true" ) {

        jQuery( '#wpi_event_amount' ).val( '' );
        jQuery( '#wpi_event_note' ).val( '' );
        jQuery( '#wpi_event_tax' ).val( '' );
        jQuery( '#wpi_event_type' ).val( 'add_payment' );
        jQuery( "#event_tax_holder" ).hide();
        jQuery( "#ajax-response" ).show();
        jQuery( "#ajax-response p" ).html( data.message );

        wpi_update_status_box();
        wpi_update_charges_list();

        // Recalculate totals if adjustment is monetary
        if ( event_data.event_type == 'add_payment' ) {
          window.adjustments -= event_data.event_amount;
        }
        if ( event_data.event_type == 'do_adjustment' ) {
          window.adjustments -= event_data.event_amount;
        }

        wpi_recalc_totals();

      } else {
        jQuery( ".wpi_ajax_response" ).addClass( 'wpi_error' );
        jQuery( ".wpi_ajax_response" ).html( data.message );
      }

    }


  } );

}
/*
 Displays email notification metabox
 */
function wpi_show_notification_box () {
  jQuery( '#send_notification_box' ).toggle();
  if ( jQuery( '#send_notification_box' ).is( ":visible" ) ) {
  } else {
  }
}
/*
 Insert a new line of pre-defined services.
 Triggered by .onchange event on dropdown
 */
function wpi_insert_predefined_service () {
  add_itemized_list_row( 'invoice_list' );
  var option_value = jQuery( '#wpi_predefined_services option:selected' ).val();
  option_value = option_value.split( "|" );
  var name = option_value[0];
  var description = option_value[1];
  var quantity = option_value[2];
  var price = option_value[3];
  var tax = option_value[4];

  /* Clean Global tax if values are different */
  var global_tax = jQuery( '#wp_invoice_tax' ).val();
  if ( global_tax > 0 && tax != global_tax ) {
    jQuery( '#wp_invoice_tax' ).val( '' );
  }

  jQuery( '#invoice_list li.wp_invoice_itemized_list_row:last input.item_name' ).val( name );
  jQuery( '#invoice_list li.wp_invoice_itemized_list_row:last input.item_quantity' ).val( quantity );
  jQuery( '#invoice_list li.wp_invoice_itemized_list_row:last input.item_price' ).val( price );
  jQuery( '#invoice_list li.wp_invoice_itemized_list_row:last input.line_tax_item' ).val( tax );
  // Show description if one is iset
  if ( description != '' ) {
    jQuery( "#invoice_list li.wp_invoice_itemized_list_row:last .item_description" ).show();
    jQuery( '#invoice_list li.wp_invoice_itemized_list_row:last .item_description' ).val( description );
  }
  setTimeout(function(){
    jQuery( "#wpi_predefined_services" ).val( '' );
    jQuery( "#wpi_predefined_services" ).select2('data', null);
  }, 100);
  wpi_recalc_totals();
}
/*
 Load email notification via ajax and display on the invoice page for sending
 */
function wpi_load_email_notification () {
  var selected = jQuery( "#wpi_change_notification option:selected" ).val();
  var invoice_id;
  // Get invoice id. If this is a new invoice it will be returned with update message as #new_invoice_id
  // If existing invoice its stored in input field #wpi_invoice_id
  var invoice_id = jQuery( "#wpi_invoice_id" ).val();
  if ( empty( invoice_id ) )
    invoice_id = jQuery( "#new_invoice_id" ).text();
  if ( selected ) {
    jQuery( "#wpi_template_loading" ).show();
    template_id_val = selected;
    jQuery.getJSON( ajaxurl, {action: 'wpi_get_notification_email', template_id: template_id_val, wpi_invoiceid: invoice_id}, function ( response ) {
      jQuery( '#wpi_notification_message' ).val( response.wpi_content );
      jQuery( '#wpi_notification_subject' ).val( response.wpi_subject );
      jQuery( "#wpi_template_loading" ).hide();
    } );
  } else {
    return;
  }
}
/*
 Send notification to client via ajax
 */
function wpi_send_notification () {
  /* Show the loading box */
  jQuery( "#wpi_template_loading" ).show();
  jQuery.ajax( {
    data: {
      action: "wpi_send_notification",
      subject: jQuery( "#wpi_notification_subject" ).val(),
      invoice_id: jQuery( "#wpi_invoice_id" ).val(),
      template: jQuery( "#wpi_change_notification option:selected" ).text(),
      to: jQuery( "#wpi_notification_send_to" ).val(),
      body: jQuery( "#wpi_notification_message" ).val()
    },
    type: "POST",
    url: ajaxurl,
    success: function ( data ) {
      if ( data.status == 200 ) {
        wpi_show_success( data.msg );
      } else {
        wpi_show_error( data.msg );
      }
      //Hide the box
      jQuery( "#send_notification_box" ).hide();
      // Hide the AJAX div
      jQuery( "#wpi_template_loading" ).hide();
      // Update status box
      wpi_update_status_box();
      // Scroll to the top
      jQuery( 'html, body' ).animate( {scrollTop: 0}, 'slow' );
    },
    dataType: 'json'
  } );
}

function checkdate ( input ) {
  var validformat = /^\d{2}\/\d{2}\/\d{4}$/; //Basic check for format validity
  if ( !validformat.test( input ) ) {
    return false;
  } else { //Detailed check for valid date ranges
    var monthfield = input.split( "/" )[0];
    var dayfield = input.split( "/" )[1];
    var yearfield = input.split( "/" )[2];
    var dayobj = new Date( yearfield, monthfield - 1, dayfield );
    if ( (dayobj.getMonth() + 1 != monthfield) || (dayobj.getDate() != dayfield) || (dayobj.getFullYear() != yearfield) ) {
      return false;
    } else {
      return true;
    }
  }
}

function wpi_validate_invoice () {
  var validated = true;

  if ( jQuery( '[name^="wpi_invoice[subject]"]' ).val() == '' ) {
    jQuery( '[name^="wpi_invoice[subject]"]' ).addClass( 'wpi_error' );
    validated = false;
  } else {
    jQuery( '[name^="wpi_invoice[subject]"]' ).removeClass( 'wpi_error' );
  }

  jQuery( "input.item_name:visible", "#charges_list" ).each( function ( k, v ) {
    jQuery( v ).removeClass( 'wpi_error' );
    if ( empty( jQuery( v ).val() ) ) {
      jQuery( v ).addClass( 'wpi_error' );
      validated = false;
    }
  } );
  jQuery( "input.item_amount:visible", "#charges_list" ).each( function ( k, v ) {
    jQuery( v ).removeClass( 'wpi_error' );
    if ( empty( jQuery( v ).val() ) ) {
      jQuery( v ).addClass( 'wpi_error' );
      validated = false;
    }
  } );
  jQuery( "input.item_charge_tax:visible", "#charges_list" ).each( function ( k, v ) {
    jQuery( v ).removeClass( 'wpi_error' );
    if ( isNaN( jQuery( v ).val() ) || jQuery( v ).val() < 0 ) {
      jQuery( v ).addClass( 'wpi_error' );
      validated = false;
    }
  } );

  return validated;
}

function wpi_save_invoice () {
  var invoice_data;
  var radios = {};
  // primary data to set things up
  invoice_data = {
    action: "wpi_save_invoice",
    nonce: jQuery( '#wpi-update-invoice' ).val()
  };
  // select all input fields
  jQuery( '[name^="wpi_invoice"]' ).each( function () {
    invoice_data[this.name] = this.value;
    if ( jQuery( this ).is( ':checkbox' ) ) {
      if ( jQuery( this ).is( ':checked' ) ) {
        invoice_data[this.name] = 'on';
      } else {
        invoice_data[this.name] = 'off';
      }
    }
    if ( jQuery( this ).is( ':radio' ) ) {
      radios[this.value] = jQuery( this );
    }
  } );

  jQuery.each( radios, function( k, radio ) {
    if ( radio.is(':checked') ) {
      invoice_data[radio.attr('name')] = k;
    }
  });

  // Get data from MCE Editor
  invoice_data['wpi_invoice[description]'] = jQuery( 'textarea[name="content"]' ).val();

  jQuery.ajax( {
    data: invoice_data,
    beforeSend: function () {
    },
    type: "POST",
    url: ajaxurl,
    success: function ( data ) {

      // response handler function if ugly. I don't want to refactor all this shit right now. Just adding this error checker. korotkov@ud
      if ( typeof data == 'object' ) {
        if ( !data.success ) {
          jQuery( "#ajax-response" ).removeClass('update').addClass('error').show();
          jQuery( "#ajax-response p" ).html( data.data.message );
          jQuery('html').animate({scrollTop: jQuery('body').offset().top});
          return;
        }
      }

      jQuery( "#ajax-response" ).addClass('update').removeClass('error').show();
      jQuery( "#ajax-response p" ).html( data );
      // Show invoice message box
      // Show invoice log
      jQuery( "#send_notification_box" ).show();
      jQuery( "#postbox_status_and_history" ).show();
      jQuery( ".wpi_hide_until_saved" ).show();

      /* If data contains invoice's premalink, we update it in editor */
      var dom = "<div>" + data + "</div>";
      var a = jQuery( "a", dom );

      if ( a.length > 0 ) {

        var permalink = a.attr( 'href' );
        if ( jQuery( '#sample-permalink' ).length > 0 ) {
          jQuery( '#sample-permalink' ).html( permalink );
        }

      }

      jQuery( "a.wpi_update_with_invoice_url" ).each( function () {
        var url_annex;

        if ( jQuery( this ).attr( 'url_annex' ) ) {
          url_annex = jQuery( this ).attr( 'url_annex' );
        } else {
          url_annex = '';
        }

        jQuery( this ).attr( 'href', permalink + url_annex );

      } );

      jQuery('html').animate({scrollTop: jQuery('body').offset().top});

      // Update status box
      wpi_update_status_box();
    }
  } );

  // Hide Payment postbox if quote checkbox is checked
  if ( jQuery( "#wpi_wpi_invoice_quote_" ).is( ':checked' ) ) {
    jQuery( "#postbox_payment_methods" ).hide();
  } else {
    jQuery( "#postbox_payment_methods" ).show();
  }

  // Enter values into send notification box
  if ( empty( jQuery( "#wpi_notification_send_to" ).val() ) ) {
    jQuery( "#wpi_notification_send_to" ).val( jQuery( "#wpi_user_email" ).text() );
  }
  if ( empty( jQuery( "#wpi_notification_subject" ).val() ) ) {
    jQuery( "#wpi_notification_subject" ).val( jQuery( "#title" ).val() );
  }
  return false;
}

/*
 Update invoice history status box via ajax call
 */
function wpi_update_status_box () {
  jQuery.post( ajaxurl, {
    action: 'wpi_get_status',
    invoice_id: jQuery( '#wpi_invoice_id' ).val()
  }, function ( data ) {
    jQuery( "#wpi_invoice_status_table" ).html( data );
    if ( is_recurring )
      jQuery( ".wpi_not_for_recurring" ).hide();
  } );
}

/*

 */
function wpi_update_charges_list () {
  jQuery.post( ajaxurl, {
    action: 'wpi_get_charges',
    invoice_id: jQuery( '#wpi_invoice_id' ).val()
  }, function ( data ) {
    jQuery( "#charges_list .wp_invoice_itemized_charge_row" ).remove();
    jQuery( "#charges_list" ).append( data ).show();
    wpi_recalc_totals();
  } );
}

function wp_invoice_add_time ( target, add_days ) {
  if ( add_days == 'clear' ) {
    jQuery( "#wpi_invoice_form #" + target + "_mm" ).val( '' );
    jQuery( "#wpi_invoice_form #" + target + "_jj" ).val( '' );
    jQuery( "#wpi_invoice_form #" + target + "_aa" ).val( '' );
  } else {
    var myDate = new Date();
    var week_from_now = new Date( myDate.getTime() + add_days * 24 * 60 * 60 * 1000 );
    ;
    var month = week_from_now.getMonth() + 1;

    // make month two digit
    month = (month < 10 ? '0' : '') + month;
    var day = (week_from_now.getDate() < 10 ? '0' : '') + week_from_now.getDate();

    jQuery( "#wpi_invoice_form #" + target + "_mm" ).val( month );
    jQuery( "#wpi_invoice_form #" + target + "_jj" ).val( day );
    jQuery( "#wpi_invoice_form #" + target + "_aa" ).val( week_from_now.getFullYear() );
  }
  return false;
}
/*
 Calculates total owed based on displayed invoices on invoice overview page
 */
function wp_invoice_calculate_owed () {
  alert( 'running wp_invoice_calculate_owed, obsolete?' );
  jQuery( "#wp_invoice_total_owed" ).html( jQuery( "#invoice_sorter_table tr:visible .row_money" ).sum() );
  jQuery( "#wp_invoice_total_owed" ).formatCurrency( {useHtml: true} );
}

/*function wpi_load_email_template() {
 if(confirm('Do you want to restore the WP-Invoice generated message?')) {
 jQuery("#wp_invoice_email_message_content").val(jQuery("#wp_invoice_email_message_content_original").val());
 }
 }*/

function wp_invoice_cancel_recurring () {
  jQuery( "#wp_invoice_subscription_name" ).val( '' );
  jQuery( "#wp_invoice_subscription_unit" ).val( '' );
  jQuery( "#wp_invoice_subscription_length" ).val( '' );
  jQuery( "#wp_invoice_subscription_start_month" ).val( '' );
  jQuery( "#wp_invoice_subscription_start_day" ).val( '' );
  jQuery( "#wp_invoice_subscription_start_year" ).val( '' );
  jQuery( "#wp_invoice_subscription_total_occurances" ).val( '' );
  //jQuery(".wp_invoice_enable_recurring_billing").toggle();
  jQuery( "#wp_invoice_enable_recurring_billing" ).toggle();
  jQuery( ".wp_invoice_enable_recurring_billing" ).toggle();
}

function wp_invoice_subscription_start_time ( add_days ) {
  function formatNum ( num ) {
    var mynum = num * 1;
    var retVal = mynum < 10 ? '0' : '';
    return (retVal + mynum)
  }

  if ( add_days == 'clear' ) {
    jQuery( "#wp_invoice_subscription_start_month" ).val( '' );
    jQuery( "#wp_invoice_subscription_start_day" ).val( '' );
    jQuery( "#wp_invoice_subscription_start_year" ).val( '' );
  } else {
    myDate = new Date();
    var week_from_now = new Date( myDate.getTime() + add_days * 24 * 60 * 60 * 1000 );
    ;
    month = week_from_now.getMonth() + 1;
    jQuery( "#wp_invoice_subscription_start_month" ).val( formatNum( month ) );
    jQuery( "#wp_invoice_subscription_start_day" ).val( week_from_now.getDate() );
    jQuery( "#wp_invoice_subscription_start_year" ).val( week_from_now.getFullYear() );
  }
  return false;
}

function wpi_disable_all_payment_methods () {
  // uncheck app payment method checkboxes
  jQuery( '.wpi_billing_section_show' ).attr( 'checked', false );
  // blank out all billing venue default_payment settings
  jQuery( ".billing-default-option" ).val( '' );
  // hide all accordion sections
  jQuery( ".wp_invoice_accordion .wp_invoice_accordion_section" ).hide();
  // Hide all notices that reappear when checkbox is checked
  jQuery( '.wpi_notice' ).hide();
}

function wpi_init_payment_method () {
  // cycle through checked methods and run the wpi_select_payment_method which will either turn them on or off
  jQuery( '.wpi_billing_section_show' ).each( function () {
    wpi_select_payment_method( jQuery( this ).attr( 'id' ), false, true );
  } );
  // if client cannot change payment method, we hide payment method checkboxes
  wpi_can_client_change_payment_method()
}

function wpi_can_client_change_payment_method () {
  if ( jQuery( '.wpi_client_change_payment_method' ).is( ":not(:checked)" ) ) {
    // hide all payment selection checkboxes
    jQuery( ".wpi-payment-setting" ).hide();
    // hide all payment options
    wpi_disable_all_payment_methods();
    // display only the default selected payment option
    wpi_select_payment_method( jQuery( '#wp_invoice_payment_method option:selected' ).val(), true, true )
  } else {
    jQuery( ".wpi-payment-setting" ).show();
  }
}

function wpi_select_payment_method ( method, force, init ) {
  var method_checked;
  // force set to true means we check the checkbox for this repsective method automatically
  if ( force ) jQuery( "#" + method + ".wpi_billing_section_show" ).attr( 'checked', true );
  if ( jQuery( "#" + method + ".wpi_billing_section_show" ).is( ":checked" ) ) {
    method_checked = true
  } else {
    method_checked = false;
  }
  if ( method_checked ) {
    // checked, we are turning payment method on
    // Set all "default payment" values to nothing
    jQuery( ".billing-default-option" ).val( '' );
    // enter in billing venue option to set it to default
    jQuery( ".billing-" + method + "-default-option" ).val( 'true' );
    // display accordion secttion if its not displayed
    jQuery( "div." + method + "-setup-section" ).show();
    // activate accordion section unless this function was ran at initilization
    if ( !init ) {
      jQuery( '.wp_invoice_accordion' ).accordion( 'option', 'active', '#' + method + '-setup-section-header' );
    }
    jQuery( "#" + method + ".wpi_billing_section_show" ).parent().parent().find( '.wpi_notice' ).show();
    jQuery( "#" + method + ".wpi_billing_section_show" ).parent().parent().find( '.wpi_notice' ).animate( {backgroundColor: 'lightyellow'}, 1000 );
  } else {
    // not checked, we are turning this thing off
    // enter in billing venue option to set it to default
    jQuery( ".billing-" + method + "-default-option" ).val( '' );
    // display accordion secttion if its not displayed
    jQuery( "div." + method + "-setup-section" ).hide();
    // this is being hidden, so we activate the default
    if ( !init ) jQuery( '.wp_invoice_accordion' ).accordion( 'option', 'active', '#' + jQuery( '#wp_invoice_payment_method option:selected' ).val() + '-setup-section-header' );
    jQuery( "#" + method + ".wpi_billing_section_show" ).parent().parent().find( '.wpi_notice' ).hide();
  }
}

function wpi_focus_payment_method ( method ) {
  jQuery( '.ui-accordion' ).accordion( 'option', 'active', '#' + method + '-setup-section-header' );
}
// initlizes delegate function
jQuery.delegate = function ( rules ) {
  return function ( e ) {
    var target = jQuery( e.target );
    for ( var selector in rules )
      if ( target.is( selector ) ) return rules[selector].apply( this, jQuery.makeArray( arguments ) );
  }
}

function add_itemized_list_row ( where ) {
  var lastRow = jQuery( '#' + where + ' .wp_invoice_itemized_list_row:last' ).clone();
  var id = parseInt( jQuery( '.id', lastRow ).html() ) + 1;
  jQuery( '.id', lastRow ).html( id );
  lastRow.attr( 'slug', id );
  if ( where == 'invoice_list' ) {
    // Update items if this is an itemized list
    jQuery( '.item_name', lastRow ).attr( 'name', 'wpi_invoice[itemized_list][' + id + '][name]' );
    jQuery( '.item_description', lastRow ).attr( 'name', 'wpi_invoice[itemized_list][' + id + '][description]' );
    jQuery( '.row_quantity input', lastRow ).attr( 'name', 'wpi_invoice[itemized_list][' + id + '][quantity]' );
    jQuery( '.row_price input', lastRow ).attr( 'name', 'wpi_invoice[itemized_list][' + id + '][price]' );
    jQuery( '.row_tax input', lastRow ).attr( 'name', 'wpi_invoice[itemized_list][' + id + '][tax]' );
    jQuery( '.row_total', lastRow ).attr( 'id', 'total_item_' + id + '' );
  }
  if ( where == 'wpi_predefined_services_div' ) {
    // Update items if this is a predefined services itemized list
    jQuery( '.item_name', lastRow ).attr( 'name', 'wpi_settings[predefined_services][' + id + '][name]' );
    jQuery( '.item_description', lastRow ).attr( 'name', 'wpi_settings[predefined_services][' + id + '][description]' );
    jQuery( '.row_quantity input', lastRow ).attr( 'name', 'wpi_settings[predefined_services][' + id + '][quantity]' );
    jQuery( '.row_price input', lastRow ).attr( 'name', 'wpi_settings[predefined_services][' + id + '][price]' );
    jQuery( '.row_tax input', lastRow ).attr( 'name', 'wpi_settings[predefined_services][' + id + '][tax]' );
    jQuery( '.row_total', lastRow ).attr( 'id', 'total_item_' + id + '' );
  }
  // Clear out all old values
  jQuery( 'input, textarea', lastRow ).val( '' );
  jQuery( '.row_total', lastRow ).text( '0' );
  // Add global tax if value is set
  if ( jQuery( "#wp_invoice_tax" ).val() != '' ) {
    global_tax = jQuery( "#wp_invoice_tax" ).val();
    jQuery( '.row_tax input', lastRow ).val( global_tax );
  }
  jQuery( '#' + where + ' li.wp_invoice_itemized_list_row:last' ).after( lastRow );
  return false;
}

/*
 * Adds Discount Row
 */
function add_itemized_list_row_discount () {
  // Count number of discount rows
  discount_count = jQuery( ".wp_invoice_discount_row" ).size();
  discounts_hidden = (jQuery( ".wp_invoice_discount_row:last" ).is( ":visible" ) ? false : true);
  // Prevent user from adding multiple percentage discounts, and mixing and matching percentages and amount discounts

  if ( discounts_hidden ) {
    jQuery( ".wp_invoice_discount_row:last" ).show();
  } else {
    // clone last row
    var lastRow = jQuery( '#invoice_list .wp_invoice_discount_row:last' ).clone();
    var id = parseInt( jQuery( '.id', lastRow ).html() ) + 1;
    jQuery( '.id', lastRow ).html( id );
    // Update items if this is an itemized list
    jQuery( '.item_name', lastRow ).attr( 'name', 'wpi_invoice[meta][discount][' + id + '][name]' );
    jQuery( '.item_amount', lastRow ).attr( 'name', 'wpi_invoice[meta][discount][' + id + '][amount]' );
    jQuery( '.item_type', lastRow ).attr( 'name', 'wpi_invoice[meta][discount][' + id + '][type]' );
    // Clear out all old values
    jQuery( 'input', lastRow ).val( '' );
    jQuery( '#invoice_list li.wpi_invoice_totals' ).before( lastRow );
  }
  return false;
}

this.tooltip = function () {
  /* CONFIG */
  xOffset = 10;
  yOffset = 20;
  // these 2 variable determine popup's distance from the cursor
  // you might want to adjust to get the right result
  /* END CONFIG */
  jQuery( ".wp_invoice_tooltip" ).hover( function ( e ) {
    this.t = this.title;
    this.title = "";
    jQuery( "body" ).append( "<p id='wp_invoice_tooltip'>" + this.t + "</p>" );
    jQuery( "#wp_invoice_tooltip" ).css( "top", (e.pageY - xOffset) + "px" ).css( "left", (e.pageX + yOffset) + "px" ).fadeIn( "fast" );
  }, function () {
    this.title = this.t;
    jQuery( "#wp_invoice_tooltip" ).remove();
  } );
  jQuery( "a.wp_invoice_tooltip" ).mousemove( function ( e ) {
    jQuery( "#tooltip" ).css( "top", (e.pageY - xOffset) + "px" ).css( "left", (e.pageX + yOffset) + "px" );
  } );
};

/*
 * Main function to calculate all the automatically generatd totals on invoice manage page.
 */
function wpi_recalc_totals () {
  // Empty all sums
  var taxable_subtotal = 0;
  var non_taxable_subtotal = 0;
  var tax_percents = [];
  var total = 0;
  var subtotal = 0;
  var total_tax = 0;
  var total_discount = 0;

  // Services itemized list
  jQuery( ".wp_invoice_itemized_list_row" ).each( function ( i ) {
    var row_price = parseFloat( jQuery( ".row_price input", this ).val() );
    row_price = row_price < 0 || isNaN( row_price ) ? '' : row_price;
    var row_quantity = parseFloat( jQuery( ".row_quantity input", this ).val() );
    row_quantity = row_quantity < 0 || isNaN( row_quantity ) ? '' : row_quantity;
    var row_tax = parseFloat( jQuery( ".row_tax input", this ).val() );
    row_tax = row_tax < 0 || isNaN( row_tax ) ? '' : row_tax;

    // Update fields with valid data
    if ( !isNaN( row_price ) )
      jQuery( ".row_price input", this ).val( row_price );
    if ( !isNaN( row_quantity ) )
      jQuery( ".row_quantity input", this ).val( row_quantity );
    if ( !isNaN( row_tax ) )
      jQuery( ".row_tax input", this ).val( row_tax );

    row_price = !isNaN( row_price ) ? row_price : 0;
    row_quantity = !isNaN( row_quantity ) ? row_quantity : 0;
    row_tax = !isNaN( row_tax ) ? row_tax : 0;

    if ( row_tax > 0 && row_price > 0 && row_quantity > 0 ) {
      taxable_subtotal += row_price * row_quantity;
      tax_percents.push( {
        'tax': row_tax,
        'qty': row_quantity,
        'prc': row_price
      } );
      var row_total = (row_price * row_quantity + row_price * row_quantity * row_tax / 100);
    } else {
      non_taxable_subtotal += row_price * row_quantity;
      var row_total = (row_price * row_quantity);
    }

    if ( !row_total ) {
      row_total = 0;
    }

    jQuery( ".row_total", this ).html( row_total );
    jQuery( ".row_total", this ).formatCurrency( {roundToDecimalPlace: 2, useHtml: true} );

  } );

  // Services itemized list
  jQuery( ".wp_invoice_itemized_charge_row" ).each( function ( i ) {
    var row_amount = parseFloat( jQuery( ".row_amount input", this ).val() );
    var row_charge_tax = parseFloat( jQuery( ".row_charge_tax input", this ).val() );

    row_amount = !isNaN( row_amount ) ? row_amount : 0;
    row_charge_tax = !isNaN( row_charge_tax ) ? row_charge_tax : 0;

    if ( row_charge_tax > 0 ) {
      taxable_subtotal += row_amount;
      //tax_percents.push(row_charge_tax);
      tax_percents.push( {
        'tax': row_charge_tax,
        'qty': 1,
        'prc': row_amount
      } );
      var row_total = (row_amount + row_amount * row_charge_tax / 100);
    } else {
      non_taxable_subtotal += row_amount;
      var row_total = row_amount;
    }

    if ( !row_total ) {
      row_total = 0;
    }

    jQuery( ".row_total", this ).html( row_total );
    jQuery( ".row_total", this ).formatCurrency( {roundToDecimalPlace: 2, useHtml: true} );

  } );

  //alert( 'Taxable: '+taxable_subtotal+' / NonTax: '+non_taxable_subtotal );

  var avg_tax = 0;
  for ( var i = 0; i < tax_percents.length; i++ ) {
    avg_tax += tax_percents[i].tax;
  }
  if ( avg_tax > 0 ) {
    avg_tax = avg_tax / tax_percents.length;
  }

  //alert( avg_tax );

  subtotal = taxable_subtotal + non_taxable_subtotal;

  //alert( subtotal );

  // Calculate Discounts if there are any
  jQuery( ".wp_invoice_discount_row:visible" ).each( function ( i ) {
    var name = jQuery( ".item_name", this );
    var type = jQuery( ".item_type select", this );
    var value = jQuery( ".item_price input", this );
    var discount_row_type = type.val();
    var discount_row_value = value.val();
    // Calculate total discount percent (by adding) or discoutn value (also by adding)
    if ( !empty( discount_row_value ) ) {
      discount_row_value = parseFloat( discount_row_value );
      discount_row_value = discount_row_value < 0 || isNaN( discount_row_value ) ? '' : discount_row_value;
      if ( !isNaN( discount_row_value ) )
        value.val( discount_row_value );
      if ( !empty( value.val() ) && !name.val().length ) {
        name.css( {'border-color': 'red'} );
      } else if ( empty( value.val() ) && name.val().length ) {
        value.css( {'border-color': 'red'} );
      } else {
        name.css( {'border-color': ''} );
        value.css( {'border-color': ''} );
      }

      if ( discount_row_type == 'percent' ) {
        total_discount = subtotal * (discount_row_value / 100);
      } else {
        total_discount += discount_row_value;
      }
    } else {
      name.css( {'border-color': ''} );
    }
  } );

  // alert( total_discount );

  var tax_method = jQuery( "#wpi_tax_method" ).val();

  switch ( tax_method ) {
    case 'before_discount':

      jQuery.each( tax_percents, function ( k, v ) {
        total_tax += v.prc / 100 * v.tax * v.qty;
      } );

      //total_tax = taxable_subtotal * avg_tax / 100;

      break;

    case 'after_discount':

      var subtotal_with_discount = subtotal - total_discount;
      var taxable_amount = taxable_subtotal / subtotal * subtotal_with_discount;
      total_tax = taxable_amount * avg_tax / 100;

      break;

    default:

      jQuery.each( tax_percents, function ( k, v ) {
        total_tax += v.prc / 100 * v.tax * v.qty;
      } );

      //total_tax = taxable_subtotal * avg_tax / 100;
      break;
  }

  total = subtotal - total_discount + total_tax;

  if ( total_discount > ( subtotal + total_tax ) && total_discount > 0 ) {
    jQuery( ".wp_invoice_discount_row:visible" ).each( function ( i ) {
      jQuery( ".item_price input", this ).val( '' );
    } );
    wpi_recalc_totals();
    return;
  }

  if ( typeof is_recurring == 'undefined' ) {
    is_recurring = false;
  }

  if ( !empty( window.adjustments ) && window.adjustments != 0 ) {
    jQuery( '.column-invoice-details-adjustments' ).show();
    jQuery( '.column-invoice-details-subtotal' ).show();
    adjustments = parseFloat( adjustments );
    total += adjustments;
  } else {
    jQuery( '.column-invoice-details-adjustments' ).hide();
    if ( window.adjustments == '0' ) {
      adjustments = '0';
    } else {
      // There is no return from payment/charge update. we use our original value
      adjustments = jQuery( ".calculate_invoice_adjustments" ).val();
      // Remove dollar symbol to make math possible
      if ( !empty( adjustments ) ) {
        adjustments = parseFloat( adjustments.replace( "$", "" ) );
      }
    }
  }

  var total_due = total;

  if ( !empty( total_tax ) ) {
    jQuery( '.column-invoice-details-tax' ).show();
    jQuery( '.column-invoice-details-subtotal' ).show();
  } else {
    jQuery( '.column-invoice-details-tax' ).hide();
    // dont hide subtotal it may be displayed of other reasons - adjustments
  }

  if ( !empty( total_discount ) ) {
    jQuery( '.column-invoice-details-discounts' ).show();
  } else {
    jQuery( '.column-invoice-details-discounts' ).hide();
    // dont hide subtotal it may be displayed of other reasons - adjustments
  }

  // Fix total is this is recurring. Note: on recurring bills, adjustments are not added to the balance
  if ( is_recurring ) {
    jQuery( ".column-invoice-details-adjustments" ).hide();
    /*
     * @TODO: There are no any place where discount_amount and tax_weighted_average variables is initialized.
     * Where are these variables from?
     * I commented out code below for the current moment to avoid js errors because of undefined variables.
     * But it should be revised and fixed!!! Maxim Peshkov
     */
    //total_tax = (subtotal  - (!empty(discount_amount) ? discount_amount : 0)) * tax_weighted_average;
    //total_due = subtotal + total_tax - (!empty(discount_amount) ? discount_amount : 0);
  } else {
    // re-evaluate if adjustments should be displayed
    if ( !empty( adjustments ) ) {
      jQuery( '.column-invoice-details-adjustments' ).show();
    }
  }

  jQuery( ".calculate_invoice_adjustments" ).val( adjustments ).formatCurrency( {roundToDecimalPlace: 2} );
  jQuery( ".calculate_invoice_subtotal" ).val( subtotal ).formatCurrency( {roundToDecimalPlace: 2} );
  jQuery( ".calculate_discount_total" ).val( total_discount ).formatCurrency( {roundToDecimalPlace: 2} );
  jQuery( ".calculate_invoice_tax" ).val( total_tax ).formatCurrency( {roundToDecimalPlace: 2} );
  jQuery( ".calculate_invoice_total" ).val( total_due ).formatCurrency( {roundToDecimalPlace: 2} );
}

function empty ( mixed_var ) {
  // http://kevin.vanzonneveld.net
  // +   original by: Philippe Baumann
  // +    input by: Onno Marsman
  // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +    input by: LH
  // +   improved by: Onno Marsman
  // +   improved by: Francesco
  // +   improved by: Marc Jansen
  // *   example 1: empty(null);
  // *   returns 1: true
  // *   example 2: empty(undefined);
  // *   returns 2: true
  // *   example 3: empty([]);
  // *   returns 3: true
  // *   example 4: empty({});
  // *   returns 4: true
  // *   example 5: empty({'aFunc' : function () { alert('humpty'); } });
  // *   returns 5: false
  var key;
  if ( mixed_var === "" || mixed_var === 0 || mixed_var === "0" || mixed_var === null || mixed_var === false || mixed_var === undefined ) {
    return true;
  }
  if ( typeof mixed_var == 'object' ) {
    for ( key in mixed_var ) {
      return false;
    }
    return true;
  }
  return false;
}
/*
 function SaveFavroiteCountries() {
 var favcountries = "";
 var  i = 0;
 jQuery('select#d option:selected').each(function(){
 if (i == 0) {
 favcountries = jQuery(this).val();
 } else {
 favcountries = favcountries  + ',' + jQuery(this).val();
 }
 i++;
 });
 document.getElementById('hdnFavroiteCountries').value = favcountries;
 }
 */
// This jQuery v1.1.3 plugin will return an delimited
// list of the given attribute of all elements in the
// current jQuery stack.
// Source: http://www.bennadel.com/blog/861-jQuery-Plugin-To-Return-Delimited-Value-List-Of-Stack-Element-Attributes-Follow-Up-.htm
jQuery.fn.attrList = function ( strAttribute, strDelimiter ) {
  // Create an array to store the attribute values of
  // the jQuery stack items.
  var arrValues = new Array();
  // Check to see if we were given a delimiter.
  // By default, we will use the comma.
  strDelimiter = (strDelimiter ? strDelimiter : ",");
  // Loop over each element in the jQuery stack and
  // add the given attribute to the value array.
  this.each( function ( intI ) {
    // Get a jQuery version of the current
    // stack element.
    var jNode = jQuery( this );
    // Add the given attribute value to our
    // values array.
    arrValues[ arrValues.length ] = jNode.attr( strAttribute );
  } );
  // Return the value list by joining the array.
  return(
    arrValues.join( strDelimiter )
    );
}