2

I have two radio buttons:

  • fixed_price_option (Selected by default.)
  • variable_price_option (Disabled by default)

I also have two types of inputs:

  • fixed_price_input (Visable by default. Only one occurance.)
  • variable_price_input (Not present in code as it has to be added dynamically. One or more occurances.)

When fixed_price_option is selected an input called fixed_price_input should be visable and included when later running .serialize().

When fixed_price_option is selected no variable_price_input´s should be visible or included when later running .serialize().

variable_price_option should only be selectable when the difference between two date inputs are more than 12 months. (this I have solved)

When variable_price_option is selected there should be one more variable_price_input´s visable as there are whole years between the two date inputs (i.e. durationMonths + 1). They also need to be included when later running .serialize() so they need to have names like price_year_1, price_year_2, price_year_3 and so on, depending on how many whole years there are between the two date inputs.

When variable_price_option is selected fixed_price_input should not be visible or included when later running .serialize().

I have supplied the code as far as I have come. The missing logic needs to be put in the event handler at the bottom of the js code.

Any suggestions on how to solve this?

-- UPDATE --

My question needed clarification:

What I'm struggling with is to toggle the existence of the two types of inputs (fixed_price_input and variable_price_input) depending on which radio button is checked. Hiding/showing them isn't enough because I'm going to use .serialize() at a later point. Should I use .detach() and .append() somehow?

I'm also struggling with how to create one more variable_price_input's than there are years between the start and end date. Should I use <template> or .clone() somehow?

$(document).ready(function() {

  $("#inputStartDate, #inputEndDate").change(function() {

    if ($('#inputStartDate').val() && $('#inputEndDate').val()) {

      var startDate = moment($('#inputStartDate').val());
      var endDate = moment($('#inputEndDate').val());

      var durationMonths = endDate.diff(startDate, 'months');

      $('#durationMonths').text(durationMonths);
      
      var durationYears = endDate.diff(startDate, 'years');
    
      $('#durationYears').text(durationYears);

      if (duration > 12) {
        $('#variablePriceOption').prop("disabled", false);
      } else {
        $('#variablePriceOption').prop("disabled", true);
      }

    }


  });

  $('#variablePriceOption, #fixedPriceOption').change(function() {

    if (this.value == 'fixedPrice') {
      //Logic needed
    } else if (this.value == 'variablePrice') {
      //Logic needed
    }

  });

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.0/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>

<div class="container">

  <div class="row mt-3">
    <div class="col">
      <div class="form-group">
        <label for="inputStartDate">Start date</label>
        <input type="date" class="form-control" id="inputStartDate" name="land_contract_start_date">
      </div>
    </div>
    <div class="col">
      <div class="form-group">
        <label for="inputEndDate">End date</label>
        <input type="date" class="form-control" id="inputEndDate" name="land_contract_end_date">
      </div>
    </div>
  </div>

  <div class="text-center">Months between selected dates = <span id="durationMonths"></span>. Years between selected dates = <span id="durationYears"></span>.
  </div>

  <div class="form-group">
    <label for="inputPriceModel">Price model</label>
    <div id="inputPriceModel">
      <div class="form-check">
        <input class="form-check-input" type="radio" name="inputPriceModel" id="fixedPriceOption" value="fixedPrice" required checked="checked">
        <label class="form-check-label" for="fixedPriceOption">
          Fixed price
        </label>
      </div>
      <div class="form-check">
        <input class="form-check-input" type="radio" name="inputPriceModel" id="variablePriceOption" value="variablePrice" disabled="disabled">
        <label class="form-check-label" for="variablePriceOption">
          Variable price
        </label>
      </div>
    </div>
  </div>

  <div class="form-group fixedPriceModelFormGroup">
    <label for="fixed_price_input">Fixed price amount</label>
    <div class="input-group">
      <input type="number" class="form-control" id="fixed_price_input" name="land_contract_fixed_annual_price">
      <div class="input-group-append">
        <span class="input-group-text">$</span>
      </div>
    </div>
  </div>

</div>
Rawland Hustle
  • 781
  • 1
  • 10
  • 16
  • 2
    I have updated/clarified my question now. – Rawland Hustle Jul 22 '19 at 19:03
  • 2
    You can use `.detach()` yet you'll want to store that object data someplace if the need arises to create it again. – Twisty Jul 22 '19 at 19:21
  • 1
    @Twisty Thanks! I was thinking about using this solution somehow but I couldn't figure out how to implement it in this case: https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_html_detach_move. – Rawland Hustle Jul 22 '19 at 19:27

1 Answers1

1

This should help get you started as far as variable pricing inputs showing for each # of year difference of the calendar dates. The code could be broken out into other functions for handling the display/hiding of elements, etc. You need to move your <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> code above your other JS references to get rid of the errors you're seeing for bootstrap.

Also, your duration variable should be durationMonths for comparing > 12, as duration is undefined. durationYears should be moved outside the change function of the calendar dates so you can reference it in your other processing functions. I added Math.abs() to the date calculations to ensure you're dealing with a positive integer for comparisons.

Using the disabled attribute on the inputs that are hidden will allow you to serialize the visible form data and ensure you won't get hidden inputs (variable pricing fields, etc) as part of the serialization data.

As @Twisty mentioned in the comments on your post, you will want to use .detach() or some sort of way to store the variable pricing input values if you toggle back and forth between Fixed/Variable options (localStorage, sessionStorage also options for storing data), if you want to maintain any values placed in the variable/fixed inputs. You will need to remove the .empty() usage on the input fields in my example as well, if you intend to store the data values of the inputs.

The loop function handleVariablePricing for determining how many variable pricing inputs should show would need to hook into the stored data functionality to ensure you are creating the same amount of fields with previously entered values, and not adding additional new fields on top of the existing fields/values.

$(document).ready(function() {

  var durationYears = 0;

  $("#inputStartDate, #inputEndDate").change(function() {

    if ($('#inputStartDate').val() && $('#inputEndDate').val()) {

      var startDate = moment($('#inputStartDate').val());
      var endDate = moment($('#inputEndDate').val());

      var durationMonths = Math.abs(endDate.diff(startDate, 'months'));
      $('#durationMonths').text(durationMonths);

      // maintain value outside of change function
      durationYears = Math.abs(endDate.diff(startDate, 'years'));
      $('#durationYears').text(durationYears);

      if (durationMonths > 12) {
        $('#variablePriceOption').prop("disabled", false);
      } else {
        $('#variablePriceOption').prop("disabled", true);
      }

      // If dates changed, update variable inputs shown
      if ($('#variablePriceOption').is(':checked')) {
        if (durationMonths > 12) {
          $('#variable_price_input_1').val('');
          $('.duration-years-input').remove();
          handleVariablePricing();
        } else {
          $('#fixedPriceOption').click();
        }
      }
    }

  });

  $('#variablePriceOption, #fixedPriceOption').change(function() {

    if (this.value == 'fixedPrice') {

      $('.variablePriceModelFormGroup').removeClass('d-block').addClass('d-none');
      $('.variablePriceModelFormGroup input').each(function() {
        $(this).val('').attr('disabled', true);
      });
      $('.fixedPriceModelFormGroup input').prop('disabled', false);
      $('.fixedPriceModelFormGroup').removeClass('d-none').addClass('d-block');
      $('.duration-years-input').remove();

    } else if (this.value == 'variablePrice') {

      $('.fixedPriceModelFormGroup').removeClass('d-block').addClass('d-none');
      $('.fixedPriceModelFormGroup input').val('').attr('disabled', true);
      $('#variable_price_input_1').prop('disabled', false);
      $('.variablePriceModelFormGroup').removeClass('d-none').addClass('d-block');

      handleVariablePricing();

    }

  });

  /**
   * Creates inputs for variable pricing..
   **/
  var handleVariablePricing = function() {
    $rowClone = $('.row-main').clone();
    for (var i = 2; i <= durationYears + 1; i++) {
      $rowClone.prop('class', 'duration-years-input');
      $rowClone.find('label').text('Price Year ' + i);
      $rowClone.find('input').prop('id', 'variable_price_input_' + i);
      $rowClone.find('input').prop('name', 'land_contract_variable_annual_price_' + i);
      if ($('.duration-years-input').length === 0) {
        $('.row-main').after($rowClone);
      } else {
        $('.duration-years-input').last().after($rowClone);
      }
      $rowClone = $('.duration-years-input').last().clone();
    }
  };

  $('button').click(function() {
    console.log($('#test-form').serialize());
  });

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.0/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>

<div class="container">
  <form id="test-form">
    <div class="row mt-3">
      <div class="col">
        <div class="form-group">
          <label for="inputStartDate">Start date</label>
          <input type="date" class="form-control" id="inputStartDate" name="land_contract_start_date">
        </div>
      </div>
      <div class="col">
        <div class="form-group">
          <label for="inputEndDate">End date</label>
          <input type="date" class="form-control" id="inputEndDate" name="land_contract_end_date">
        </div>
      </div>
    </div>
    <div class="text-center">Months between selected dates = <span id="durationMonths"></span>. Years between selected dates = <span id="durationYears"></span>.
    </div>
    <div class="form-group">
      <label for="inputPriceModel">Price model</label>
      <div id="inputPriceModel">
        <div class="form-check">
          <input class="form-check-input" type="radio" name="inputPriceModel" id="fixedPriceOption" value="fixedPrice" required checked="checked">
          <label class="form-check-label" for="fixedPriceOption">
          Fixed price
        </label>
        </div>
        <div class="form-check">
          <input class="form-check-input" type="radio" name="inputPriceModel" id="variablePriceOption" value="variablePrice" disabled="disabled">
          <label class="form-check-label" for="variablePriceOption">
          Variable price
        </label>
        </div>
      </div>
    </div>
    <div class="form-group fixedPriceModelFormGroup">
      <label for="fixed_price_input">Fixed price amount</label>
      <div class="input-group">
        <input type="number" class="form-control" id="fixed_price_input" name="land_contract_fixed_annual_price">
        <div class="input-group-append">
          <span class="input-group-text">$</span>
        </div>
      </div>
    </div>
    <div class="form-group variablePriceModelFormGroup d-none">
      <div class="row-main">
        <label for="variable_price_input">Price Year 1</label>
        <div class="input-group">
          <input type="number" class="form-control" id="variable_price_input_1" name="land_contract_variable_annual_price_1" disabled="disabled">
          <div class="input-group-append">
            <span class="input-group-text">$</span>
          </div>
        </div>
      </div>
    </div>
  </form>
  <button>Serialize</button>
</div>
Woodrow
  • 2,740
  • 1
  • 14
  • 18
  • 1
    If you want an additional input for variable pricing on top of # of years, just add 1 to the durationYears variable for the for loop. – Woodrow Jul 22 '19 at 20:07
  • 1
    Wow, thank you very much! I forgot to mention that the label for each `variable_price_input` need to be descriptive, like "Price year 1", "Price year 2" and so on. Anyway to fix that? Also, how can I have the number of `variable_price_input`´s adjust when #variablePriceOption is checked and the start or end date is changed? – Rawland Hustle Jul 22 '19 at 20:09
  • 1
    Another thing, is the custom css needed when using Bootstrap 4? Can't I just use .visible and .invisible? https://getbootstrap.com/docs/4.3/utilities/visibility/ – Rawland Hustle Jul 22 '19 at 20:10
  • 1
    Wrong classes in my last comment. I meant .d-none and .d-block. https://getbootstrap.com/docs/4.3/utilities/display/ – Rawland Hustle Jul 22 '19 at 20:20
  • 1
    I really appreciate your help. I don't care about keeping the inputted values when toggling so your solution is perfect! Let me know if you think .d-none and .d-block would work instead of custom css. Thank you! – Rawland Hustle Jul 22 '19 at 20:29
  • 1
    You're most welcome. I updated snippet with the .d-none/block classes, as yes, that makes more sense to use. – Woodrow Jul 22 '19 at 20:30
  • 1
    Awesome! Thanks again. – Rawland Hustle Jul 22 '19 at 20:31
  • 1
    I just realised that one `variable_price_input` is still showing even if I change "durationYears" to less than 1 by changing start or end date (i.e. when `variable_price_option` gets disabled again). Is there a way to make `fixed_price_option` checked automatically when "durationYears" is less than 1? – Rawland Hustle Jul 22 '19 at 20:52
  • Good Catch! Updated it so if `durationYears` is not > 0, that the Fixed Years option will automatically be clicked and the variable inputs removed, etc. – Woodrow Jul 22 '19 at 20:59
  • 1
    If I set start date to "2019-07-22" (I'm in Sweden) and end date to "2020-07-22", the `variable_price_option` is still disabled (which is correct, since the difference is not more than 1 year). If I then change end date to "2020-08-22", the `variable_price_option` is checkable (which is correct, since the difference now more than 1 year). However, if I now change end date back to "2020-07-22", `variable_price_option` is still checked (even though it's disabled) and two `variable_price_input`´s are showing. See screenshot: https://imgur.com/WUYAIPR – Rawland Hustle Jul 22 '19 at 21:20
  • Ah, I see. I updated the snippet so that it checks `if (durationMonths > 12) {` instead of `durationYears` for the `if ($('#variablePriceOption').is(':checked')) {` code block. That's my bad. – Woodrow Jul 22 '19 at 21:28
  • Welcome! Also, I know the serializing is shown as an example, but if you're looking to post the inputs using PHP or another server side language, I would update the code for the variable input fields to have something like `name="variable_pricing[]"` for each one, so you can post the values as an array, etc. Not sure if that might be helpful for what you're doing. – Woodrow Jul 22 '19 at 21:49
  • I'm not sure I understand. I'm sending the serialized form as a POST request to a PHP file which inserts the data to a MySQL database. There are ten columns in the database for the variable annual pricing: `land_contract_price_year_1`, `land_contract_price_year_2` and so on. I'm pretty sure that's the wrong way to store them but I don't know any other way. Are you suggesting I can store them as an array? – Rawland Hustle Jul 23 '19 at 08:15
  • I created a new question from my last comment: https://stackoverflow.com/questions/57161730/storing-an-array-of-unknown-length/57161907. – Rawland Hustle Jul 23 '19 at 11:30
  • 1
    I found this article and I now understand what you mean. I went for `$rowClone.find('input').prop('name', 'land_contract_variable_annual_price[year' + i + ']');`. Thanks for the tip! https://www.designedbyaturtle.co.uk/submit-an-array-with-a-html-form/ – Rawland Hustle Jul 23 '19 at 13:32